Commit e3f436dc4378940337d8a2146e599fee2125eec4

Authored by Dmitriy Zaporozhets
2 parents 70f17624 974281d1

Merge branch 'refactoring/issues_filter' into 'master'

Refactoring: Issues/MR filtering logic

Move all issues, mr filtering logic into FilteringService
app/contexts/filter_context.rb
... ... @@ -1,58 +0,0 @@
1   -class FilterContext
2   - attr_accessor :klass, :current_user, :params
3   -
4   - def initialize(klass, current_user, params)
5   - @klass = klass
6   - @current_user = current_user
7   - @params = params
8   - end
9   -
10   - def execute
11   - items = by_scope
12   - items = by_state(items)
13   - items = by_project(items)
14   - items = by_search(items)
15   - end
16   -
17   - private
18   -
19   - def by_scope
20   - table_name = klass.table_name
21   -
22   - case params[:scope]
23   - when 'authored' then
24   - current_user.send(table_name)
25   - when 'all' then
26   - klass.of_projects(current_user.authorized_projects.pluck(:id))
27   - else
28   - current_user.send("assigned_#{table_name}")
29   - end
30   - end
31   -
32   - def by_state(items)
33   - case params[:status]
34   - when 'closed'
35   - items.closed
36   - when 'all'
37   - items
38   - else
39   - items.opened
40   - end
41   - end
42   -
43   - def by_project(items)
44   - if params[:project_id].present?
45   - items = items.of_projects(params[:project_id])
46   - end
47   -
48   - items
49   - end
50   -
51   - def by_search(items)
52   - if params[:search].present?
53   - items = items.search(params[:search])
54   - end
55   -
56   - items
57   - end
58   -end
app/contexts/issues/list_context.rb
... ... @@ -1,39 +0,0 @@
1   -module Issues
2   - class ListContext < BaseContext
3   - attr_accessor :issues
4   -
5   - def execute
6   - @issues = @project.issues
7   -
8   - @issues = case params[:state]
9   - when 'all' then @issues
10   - when 'closed' then @issues.closed
11   - else @issues.opened
12   - end
13   -
14   - @issues = case params[:scope]
15   - when 'assigned-to-me' then @issues.assigned_to(current_user)
16   - when 'created-by-me' then @issues.authored(current_user)
17   - else @issues
18   - end
19   -
20   - @issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present?
21   - @issues = @issues.includes(:author, :project)
22   -
23   - # Filter by specific assignee_id (or lack thereof)?
24   - if params[:assignee_id].present?
25   - @issues = @issues.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
26   - end
27   -
28   - # Filter by specific milestone_id (or lack thereof)?
29   - if params[:milestone_id].present?
30   - @issues = @issues.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
31   - end
32   -
33   - # Sort by :sort param
34   - @issues = @issues.sort(params[:sort])
35   -
36   - @issues
37   - end
38   - end
39   -end
app/contexts/merge_requests_load_context.rb
... ... @@ -1,38 +0,0 @@
1   -# Build collection of Merge Requests
2   -# based on filtering passed via params for @project
3   -class MergeRequestsLoadContext < BaseContext
4   - def execute
5   - merge_requests = @project.merge_requests
6   -
7   - merge_requests = case params[:state]
8   - when 'all' then merge_requests
9   - when 'closed' then merge_requests.closed
10   - else merge_requests.opened
11   - end
12   -
13   - merge_requests = case params[:scope]
14   - when 'assigned-to-me' then merge_requests.assigned_to(current_user)
15   - when 'created-by-me' then merge_requests.authored(current_user)
16   - else merge_requests
17   - end
18   -
19   -
20   - merge_requests = merge_requests.page(params[:page]).per(20)
21   - merge_requests = merge_requests.includes(:author, :source_project, :target_project).order("created_at desc")
22   -
23   - # Filter by specific assignee_id (or lack thereof)?
24   - if params[:assignee_id].present?
25   - merge_requests = merge_requests.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
26   - end
27   -
28   - # Filter by specific milestone_id (or lack thereof)?
29   - if params[:milestone_id].present?
30   - merge_requests = merge_requests.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
31   - end
32   -
33   - # Sort by :sort param
34   - merge_requests = merge_requests.sort(params[:sort])
35   -
36   - merge_requests
37   - end
38   -end
app/controllers/dashboard_controller.rb
... ... @@ -3,6 +3,8 @@ class DashboardController &lt; ApplicationController
3 3  
4 4 before_filter :load_projects, except: [:projects]
5 5 before_filter :event_filter, only: :show
  6 + before_filter :default_filter, only: [:issues, :merge_requests]
  7 +
6 8  
7 9 def show
8 10 # Fetch only 30 projects.
... ... @@ -51,12 +53,12 @@ class DashboardController &lt; ApplicationController
51 53 end
52 54  
53 55 def merge_requests
54   - @merge_requests = FilterContext.new(MergeRequest, current_user, params).execute
  56 + @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params)
55 57 @merge_requests = @merge_requests.recent.page(params[:page]).per(20)
56 58 end
57 59  
58 60 def issues
59   - @issues = FilterContext.new(Issue, current_user, params).execute
  61 + @issues = FilteringService.new.execute(Issue, current_user, params)
60 62 @issues = @issues.recent.page(params[:page]).per(20)
61 63 @issues = @issues.includes(:author, :project)
62 64  
... ... @@ -71,4 +73,9 @@ class DashboardController &lt; ApplicationController
71 73 def load_projects
72 74 @projects = current_user.authorized_projects.sorted_by_activity.non_archived
73 75 end
  76 +
  77 + def default_filter
  78 + params[:scope] = 'assigned-to-me' if params[:scope].blank?
  79 + params[:state] = 'opened' if params[:state].blank?
  80 + end
74 81 end
... ...
app/controllers/groups_controller.rb
... ... @@ -10,6 +10,8 @@ class GroupsController &lt; ApplicationController
10 10 # Load group projects
11 11 before_filter :projects, except: [:new, :create]
12 12  
  13 + before_filter :default_filter, only: [:issues, :merge_requests]
  14 +
13 15 layout :determine_layout
14 16  
15 17 before_filter :set_title, only: [:new, :create]
... ... @@ -43,18 +45,14 @@ class GroupsController &lt; ApplicationController
43 45 end
44 46 end
45 47  
46   - # Get authored or assigned open merge requests
47 48 def merge_requests
48   - @merge_requests = FilterContext.new(MergeRequest, current_user, params).execute
49   - @merge_requests = @merge_requests.of_group(@group)
50   - @merge_requests = @merge_requests.recent.page(params[:page]).per(20)
  49 + @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params)
  50 + @merge_requests = @merge_requests.page(params[:page]).per(20)
51 51 end
52 52  
53   - # Get only assigned issues
54 53 def issues
55   - @issues = FilterContext.new(Issue, current_user, params).execute
56   - @issues = @issues.of_group(@group)
57   - @issues = @issues.recent.page(params[:page]).per(20)
  54 + @issues = FilteringService.new.execute(Issue, current_user, params)
  55 + @issues = @issues.page(params[:page]).per(20)
58 56 @issues = @issues.includes(:author, :project)
59 57  
60 58 respond_to do |format|
... ... @@ -130,4 +128,10 @@ class GroupsController &lt; ApplicationController
130 128 'group'
131 129 end
132 130 end
  131 +
  132 + def default_filter
  133 + params[:scope] = 'assigned-to-me' if params[:scope].blank?
  134 + params[:state] = 'opened' if params[:state].blank?
  135 + params[:group_id] = @group.id
  136 + end
133 137 end
... ...
app/controllers/projects/issues_controller.rb
... ... @@ -116,7 +116,10 @@ class Projects::IssuesController &lt; Projects::ApplicationController
116 116 end
117 117  
118 118 def issues_filtered
119   - @issues = Issues::ListContext.new(project, current_user, params).execute
  119 + params[:scope] = 'all' if params[:scope].blank?
  120 + params[:state] = 'opened' if params[:state].blank?
  121 + params[:project_id] = @project.id
  122 + @issues = FilteringService.new.execute(Issue, current_user, params)
120 123 end
121 124  
122 125 # Since iids are implemented only in 6.1
... ...
app/controllers/projects/merge_requests_controller.rb
... ... @@ -17,9 +17,15 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
17 17 before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
18 18  
19 19 def index
20   - sort_param = params[:sort] || 'newest'
21   - @sort = sort_param.humanize unless sort_param.empty?
22   - @merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute
  20 + params[:sort] ||= 'newest'
  21 + params[:scope] = 'all' if params[:scope].blank?
  22 + params[:state] = 'opened' if params[:state].blank?
  23 + params[:project_id] = @project.id
  24 +
  25 + @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params)
  26 + @merge_requests = @merge_requests.page(params[:page]).per(20)
  27 +
  28 + @sort = params[:sort].humanize
23 29 assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
24 30 @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
25 31 @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
... ...
app/helpers/dashboard_helper.rb
1 1 module DashboardHelper
2 2 def filter_path(entity, options={})
3 3 exist_opts = {
4   - status: params[:status],
  4 + state: params[:state],
5 5 scope: params[:scope],
6 6 project_id: params[:project_id],
7 7 }
... ...
app/services/filtering_service.rb 0 → 100644
... ... @@ -0,0 +1,123 @@
  1 +# FilteringService class
  2 +#
  3 +# Used to filter Issues and MergeRequests collections by set of params
  4 +#
  5 +# Arguments:
  6 +# klass - actual class like Issue or MergeRequest
  7 +# current_user - which user use
  8 +# params:
  9 +# scope: 'created-by-me' or 'assigned-to-me' or 'all'
  10 +# state: 'open' or 'closed' or 'all'
  11 +# group_id: integer
  12 +# project_id: integer
  13 +# milestone_id: integer
  14 +# assignee_id: integer
  15 +# search: string
  16 +# label_name: string
  17 +# sort: string
  18 +#
  19 +class FilteringService
  20 + attr_accessor :klass, :current_user, :params
  21 +
  22 + def execute(klass, current_user, params)
  23 + @klass = klass
  24 + @current_user = current_user
  25 + @params = params
  26 +
  27 + items = by_scope
  28 + items = by_state(items)
  29 + items = by_group(items)
  30 + items = by_project(items)
  31 + items = by_search(items)
  32 + items = by_milestone(items)
  33 + items = by_assignee(items)
  34 + items = by_label(items)
  35 + items = sort(items)
  36 + end
  37 +
  38 + private
  39 +
  40 + def by_scope
  41 + table_name = klass.table_name
  42 +
  43 + case params[:scope]
  44 + when 'created-by-me', 'authored' then
  45 + current_user.send(table_name)
  46 + when 'all' then
  47 + if current_user
  48 + klass.of_projects(current_user.authorized_projects.pluck(:id))
  49 + else
  50 + klass.of_projects(Project.public_only)
  51 + end
  52 + when 'assigned-to-me' then
  53 + current_user.send("assigned_#{table_name}")
  54 + else
  55 + raise 'You must specify default scope'
  56 + end
  57 + end
  58 +
  59 + def by_state(items)
  60 + case params[:state]
  61 + when 'closed'
  62 + items.closed
  63 + when 'all'
  64 + items
  65 + when 'opened'
  66 + items.opened
  67 + else
  68 + raise 'You must specify default state'
  69 + end
  70 + end
  71 +
  72 + def by_group(items)
  73 + if params[:group_id].present?
  74 + items = items.of_group(Group.find(params[:group_id]))
  75 + end
  76 +
  77 + items
  78 + end
  79 +
  80 + def by_project(items)
  81 + if params[:project_id].present?
  82 + items = items.of_projects(params[:project_id])
  83 + end
  84 +
  85 + items
  86 + end
  87 +
  88 + def by_search(items)
  89 + if params[:search].present?
  90 + items = items.search(params[:search])
  91 + end
  92 +
  93 + items
  94 + end
  95 +
  96 + def sort(items)
  97 + items.sort(params[:sort])
  98 + end
  99 +
  100 + def by_milestone(items)
  101 + if params[:milestone_id].present?
  102 + items = items.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
  103 + end
  104 +
  105 + items
  106 + end
  107 +
  108 + def by_assignee(items)
  109 + if params[:assignee_id].present?
  110 + items = items.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
  111 + end
  112 +
  113 + items
  114 + end
  115 +
  116 + def by_label(items)
  117 + if params[:label_name].present?
  118 + items = items.tagged_with(params[:label_name])
  119 + end
  120 +
  121 + items
  122 + end
  123 +end
... ...
app/views/shared/_filter.html.haml
... ... @@ -2,8 +2,8 @@
2 2 = form_tag filter_path(entity), method: 'get' do
3 3 %fieldset.scope-filter
4 4 %ul.nav.nav-pills.nav-stacked
5   - %li{class: ("active" if params[:scope].blank?)}
6   - = link_to filter_path(entity, scope: nil) do
  5 + %li{class: ("active" if params[:scope] == 'assigned-to-me')}
  6 + = link_to filter_path(entity, scope: 'assigned-to-me') do
7 7 Assigned to me
8 8 %li{class: ("active" if params[:scope] == 'authored')}
9 9 = link_to filter_path(entity, scope: 'authored') do
... ... @@ -15,14 +15,14 @@
15 15 %fieldset.status-filter
16 16 %legend State
17 17 %ul.nav.nav-pills
18   - %li{class: ("active" if params[:status].blank?)}
19   - = link_to filter_path(entity, status: nil) do
  18 + %li{class: ("active" if params[:state] == 'opened')}
  19 + = link_to filter_path(entity, state: 'opened') do
20 20 Open
21   - %li{class: ("active" if params[:status] == 'closed')}
22   - = link_to filter_path(entity, status: 'closed') do
  21 + %li{class: ("active" if params[:state] == 'closed')}
  22 + = link_to filter_path(entity, state: 'closed') do
23 23 Closed
24   - %li{class: ("active" if params[:status] == 'all')}
25   - = link_to filter_path(entity, status: 'all') do
  24 + %li{class: ("active" if params[:state] == 'all')}
  25 + = link_to filter_path(entity, state: 'all') do
26 26 All
27 27  
28 28 %fieldset
... ... @@ -36,8 +36,8 @@
36 36 %small.pull-right= entities_per_project(project, entity)
37 37  
38 38 %fieldset
39   - - if params[:status].present? || params[:project_id].present?
40   - = link_to filter_path(entity, status: nil, project_id: nil), class: 'pull-right cgray' do
  39 + - if params[:state].present? || params[:project_id].present?
  40 + = link_to filter_path(entity, state: nil, project_id: nil), class: 'pull-right cgray' do
41 41 %i.icon-remove
42 42 %strong Clear filter
43 43  
... ...
app/views/shared/_project_filter.html.haml
... ... @@ -3,8 +3,8 @@
3 3 - if current_user
4 4 %fieldset
5 5 %ul.nav.nav-pills.nav-stacked
6   - %li{class: ("active" if params[:scope].blank?)}
7   - = link_to project_filter_path(scope: nil) do
  6 + %li{class: ("active" if params[:scope] == 'all')}
  7 + = link_to project_filter_path(scope: 'all') do
8 8 Everyone's
9 9 %li{class: ("active" if params[:scope] == 'assigned-to-me')}
10 10 = link_to project_filter_path(scope: 'assigned-to-me') do
... ... @@ -16,8 +16,8 @@
16 16 %fieldset
17 17 %legend State
18 18 %ul.nav.nav-pills
19   - %li{class: ("active" if params[:state].blank?)}
20   - = link_to project_filter_path(state: nil) do
  19 + %li{class: ("active" if params[:state] == 'opened')}
  20 + = link_to project_filter_path(state: 'opened') do
21 21 Open
22 22 %li{class: ("active" if params[:state] == 'closed')}
23 23 = link_to project_filter_path(state: 'closed') do
... ...
spec/contexts/filter_context_spec.rb
... ... @@ -1,65 +0,0 @@
1   -require 'spec_helper'
2   -
3   -describe FilterContext do
4   - let(:user) { create :user }
5   - let(:user2) { create :user }
6   - let(:project1) { create(:project) }
7   - let(:project2) { create(:project) }
8   - let(:merge_request1) { create(:merge_request, author: user, source_project: project1, target_project: project2) }
9   - let(:merge_request2) { create(:merge_request, author: user, source_project: project2, target_project: project1) }
10   - let(:merge_request3) { create(:merge_request, author: user, source_project: project2, target_project: project2) }
11   - let(:issue1) { create(:issue, assignee: user, project: project1) }
12   - let(:issue2) { create(:issue, assignee: user, project: project2) }
13   - let(:issue3) { create(:issue, assignee: user2, project: project2) }
14   -
15   - before do
16   - project1.team << [user, :master]
17   - project2.team << [user, :developer]
18   - end
19   -
20   - describe 'merge requests' do
21   - before :each do
22   - merge_request1
23   - merge_request2
24   - merge_request3
25   - end
26   -
27   - it 'should filter by scope' do
28   - params = { scope: 'authored' }
29   - merge_requests = FilterContext.new(MergeRequest, user, params).execute
30   - merge_requests.size.should == 3
31   - end
32   -
33   - it 'should filter by project' do
34   - params = { project_id: project1.id, scope: 'authored' }
35   - merge_requests = FilterContext.new(MergeRequest, user, params).execute
36   - merge_requests.size.should == 1
37   - end
38   - end
39   -
40   - describe 'issues' do
41   - before :each do
42   - issue1
43   - issue2
44   - issue3
45   - end
46   -
47   - it 'should filter by all' do
48   - params = { scope: "all" }
49   - issues = FilterContext.new(Issue, user, params).execute
50   - issues.size.should == 3
51   - end
52   -
53   - it 'should filter by assignee' do
54   - params = {}
55   - issues = FilterContext.new(Issue, user, params).execute
56   - issues.size.should == 2
57   - end
58   -
59   - it 'should filter by project' do
60   - params = { project_id: project1.id }
61   - issues = FilterContext.new(Issue, user, params).execute
62   - issues.size.should == 1
63   - end
64   - end
65   -end
spec/contexts/issues/list_context_spec.rb
... ... @@ -1,73 +0,0 @@
1   -require 'spec_helper'
2   -
3   -describe Issues::ListContext do
4   -
5   - let(:user) { create(:user) }
6   - let(:project) { create(:project, creator: user) }
7   -
8   - titles = ['foo','bar','baz']
9   - titles.each_with_index do |title, index|
10   - let!(title.to_sym) { create(:issue, title: title, project: project, created_at: Time.now - (index * 60)) }
11   - end
12   -
13   - describe 'sorting' do
14   - it 'sorts by newest' do
15   - params = {sort: 'newest'}
16   -
17   - issues = Issues::ListContext.new(project, user, params).execute
18   - issues.first.should eq foo
19   - end
20   -
21   - it 'sorts by oldest' do
22   - params = {sort: 'oldest'}
23   -
24   - issues = Issues::ListContext.new(project, user, params).execute
25   - issues.first.should eq baz
26   - end
27   -
28   - it 'sorts by recently updated' do
29   - params = {sort: 'recently_updated'}
30   - baz.updated_at = Time.now + 10
31   - baz.save
32   -
33   - issues = Issues::ListContext.new(project, user, params).execute
34   - issues.first.should eq baz
35   - end
36   -
37   - it 'sorts by least recently updated' do
38   - params = {sort: 'last_updated'}
39   - bar.updated_at = Time.now - 10
40   - bar.save
41   -
42   - issues = Issues::ListContext.new(project, user, params).execute
43   - issues.first.should eq bar
44   - end
45   -
46   - describe 'sorting by milestone' do
47   - let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') }
48   - let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
49   -
50   - before :each do
51   - foo.milestone = newer_due_milestone
52   - foo.save
53   - bar.milestone = later_due_milestone
54   - bar.save
55   - end
56   -
57   - it 'sorts by most recently due milestone' do
58   - params = {sort: 'milestone_due_soon'}
59   -
60   - issues = Issues::ListContext.new(project, user, params).execute
61   - issues.first.should eq foo
62   -
63   - end
64   -
65   - it 'sorts by least recently due milestone' do
66   - params = {sort: 'milestone_due_later'}
67   -
68   - issues = Issues::ListContext.new(project, user, params).execute
69   - issues.first.should eq bar
70   - end
71   - end
72   - end
73   -end
spec/services/filtering_service_spec.rb 0 → 100644
... ... @@ -0,0 +1,65 @@
  1 +require 'spec_helper'
  2 +
  3 +describe FilteringService do
  4 + let(:user) { create :user }
  5 + let(:user2) { create :user }
  6 + let(:project1) { create(:project) }
  7 + let(:project2) { create(:project) }
  8 + let(:merge_request1) { create(:merge_request, author: user, source_project: project1, target_project: project2) }
  9 + let(:merge_request2) { create(:merge_request, author: user, source_project: project2, target_project: project1) }
  10 + let(:merge_request3) { create(:merge_request, author: user, source_project: project2, target_project: project2) }
  11 + let(:issue1) { create(:issue, assignee: user, project: project1) }
  12 + let(:issue2) { create(:issue, assignee: user, project: project2) }
  13 + let(:issue3) { create(:issue, assignee: user2, project: project2) }
  14 +
  15 + before do
  16 + project1.team << [user, :master]
  17 + project2.team << [user, :developer]
  18 + end
  19 +
  20 + describe 'merge requests' do
  21 + before :each do
  22 + merge_request1
  23 + merge_request2
  24 + merge_request3
  25 + end
  26 +
  27 + it 'should filter by scope' do
  28 + params = { scope: 'authored', state: 'opened' }
  29 + merge_requests = FilteringService.new.execute(MergeRequest, user, params)
  30 + merge_requests.size.should == 3
  31 + end
  32 +
  33 + it 'should filter by project' do
  34 + params = { project_id: project1.id, scope: 'authored', state: 'opened' }
  35 + merge_requests = FilteringService.new.execute(MergeRequest, user, params)
  36 + merge_requests.size.should == 1
  37 + end
  38 + end
  39 +
  40 + describe 'issues' do
  41 + before :each do
  42 + issue1
  43 + issue2
  44 + issue3
  45 + end
  46 +
  47 + it 'should filter by all' do
  48 + params = { scope: "all", state: 'opened' }
  49 + issues = FilteringService.new.execute(Issue, user, params)
  50 + issues.size.should == 3
  51 + end
  52 +
  53 + it 'should filter by assignee' do
  54 + params = { scope: "assigned-to-me", state: 'opened' }
  55 + issues = FilteringService.new.execute(Issue, user, params)
  56 + issues.size.should == 2
  57 + end
  58 +
  59 + it 'should filter by project' do
  60 + params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
  61 + issues = FilteringService.new.execute(Issue, user, params)
  62 + issues.size.should == 1
  63 + end
  64 + end
  65 +end
... ...