Commit 00b280c3f9521f3e001b2ffc8a8a52f2a93a69d9

Authored by randx
1 parent d63706d7

Feature: Bulk Issues update

app/assets/javascripts/application.js
@@ -52,14 +52,6 @@ $(document).ready(function(){ @@ -52,14 +52,6 @@ $(document).ready(function(){
52 } 52 }
53 }); 53 });
54 54
55 - $("#issues-table .issue").live('click', function(e){  
56 - if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {  
57 - location.href = $(this).attr("url");  
58 - e.stopPropagation();  
59 - return false;  
60 - }  
61 - });  
62 -  
63 /** 55 /**
64 * Focus search field by pressing 's' key 56 * Focus search field by pressing 's' key
65 */ 57 */
app/assets/javascripts/issues.js
@@ -67,6 +67,10 @@ function initIssuesSearch() { @@ -67,6 +67,10 @@ function initIssuesSearch() {
67 */ 67 */
68 function issuesPage(){ 68 function issuesPage(){
69 initIssuesSearch(); 69 initIssuesSearch();
  70 + $("#update_status").chosen();
  71 + $("#update_assignee_id").chosen();
  72 + $("#update_milestone_id").chosen();
  73 +
70 $("#label_name").chosen(); 74 $("#label_name").chosen();
71 $("#assignee_id").chosen(); 75 $("#assignee_id").chosen();
72 $("#milestone_id").chosen(); 76 $("#milestone_id").chosen();
@@ -94,4 +98,29 @@ function issuesPage(){ @@ -94,4 +98,29 @@ function issuesPage(){
94 }); 98 });
95 99
96 }); 100 });
  101 +
  102 + $(".check_all_issues").click(function () {
  103 + $('.selected_issue').attr('checked', this.checked);
  104 + issuesCheckChanged();
  105 + });
  106 +
  107 + $('.selected_issue').bind('change', issuesCheckChanged);
  108 +}
  109 +
  110 +function issuesCheckChanged() {
  111 + var checked_issues = $('.selected_issue:checked');
  112 +
  113 + if(checked_issues.length > 0) {
  114 + var ids = []
  115 + $.each(checked_issues, function(index, value) {
  116 + ids.push($(value).attr("data-id"));
  117 + })
  118 + $('#update_issues_ids').val(ids);
  119 + $('.issues_filters').hide();
  120 + $('.issues_bulk_update').show();
  121 + } else {
  122 + $('#update_issues_ids').val([]);
  123 + $('.issues_bulk_update').hide();
  124 + $('.issues_filters').show();
  125 + }
97 } 126 }
app/assets/stylesheets/sections/issues.scss
@@ -30,6 +30,13 @@ @@ -30,6 +30,13 @@
30 .issue { 30 .issue {
31 padding:7px 10px; 31 padding:7px 10px;
32 32
  33 + .issue_check {
  34 + float:left;
  35 + padding: 8px 0;
  36 + padding-right: 8px;
  37 + min-width: 15px;
  38 + }
  39 +
33 p { 40 p {
34 padding-top:0; 41 padding-top:0;
35 padding-bottom:2px; 42 padding-bottom:2px;
@@ -41,3 +48,28 @@ @@ -41,3 +48,28 @@
41 } 48 }
42 } 49 }
43 } 50 }
  51 +
  52 +input.check_all_issues {
  53 + float:left;
  54 + padding: 8px 0;
  55 + margin: 14px 0;
  56 + margin-right: 10px;
  57 +}
  58 +
  59 +#issues-table-holder {
  60 + .issues_bulk_update {
  61 + padding: 0 5px;
  62 + margin: 0;
  63 + form {
  64 + margin:0;
  65 + padding-bottom:5px;
  66 + }
  67 + .update_selected_issues {
  68 + position:relative;
  69 + top:-2px;
  70 + margin-left:3px;
  71 + }
  72 +
  73 +
  74 + }
  75 +}
app/contexts/base_context.rb
@@ -4,5 +4,17 @@ class BaseContext @@ -4,5 +4,17 @@ class BaseContext
4 def initialize(project, user, params) 4 def initialize(project, user, params)
5 @project, @current_user, @params = project, user, params.dup 5 @project, @current_user, @params = project, user, params.dup
6 end 6 end
  7 +
  8 + def abilities
  9 + @abilities ||= begin
  10 + abilities = Six.new
  11 + abilities << Ability
  12 + abilities
  13 + end
  14 + end
  15 +
  16 + def can?(object, action, subject)
  17 + abilities.allowed?(object, action, subject)
  18 + end
7 end 19 end
8 20
app/contexts/issues_bulk_update_context.rb 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +class IssuesBulkUpdateContext < BaseContext
  2 + def execute
  3 + update_data = params[:update]
  4 +
  5 + issues_ids = update_data[:issues_ids].split(",")
  6 + milestone_id = update_data[:milestone_id]
  7 + assignee_id = update_data[:assignee_id]
  8 + status = update_data[:status]
  9 +
  10 + opts = {}
  11 + opts[:milestone_id] = milestone_id if milestone_id.present?
  12 + opts[:assignee_id] = assignee_id if assignee_id.present?
  13 + opts[:closed] = (status == "closed") if status.present?
  14 +
  15 + issues = Issue.where(:id => issues_ids).all
  16 + issues = issues.select { |issue| can?(current_user, :modify_issue, issue) }
  17 + issues.each { |issue| issue.update_attributes(opts) }
  18 + {
  19 + :count => issues.count,
  20 + :success => !issues.count.zero?
  21 + }
  22 + end
  23 +end
  24 +
app/controllers/issues_controller.rb
@@ -113,6 +113,11 @@ class IssuesController &lt; ApplicationController @@ -113,6 +113,11 @@ class IssuesController &lt; ApplicationController
113 render :partial => 'issues' 113 render :partial => 'issues'
114 end 114 end
115 115
  116 + def bulk_update
  117 + result = IssuesBulkUpdateContext.new(project, current_user, params).execute
  118 + redirect_to :back, :notice => "#{result[:count]} issues updated"
  119 + end
  120 +
116 protected 121 protected
117 122
118 def issue 123 def issue
app/views/issues/_issues.html.haml
@@ -12,3 +12,4 @@ @@ -12,3 +12,4 @@
12 - else 12 - else
13 %li 13 %li
14 %h4.nothing_here_message Nothing to show here 14 %h4.nothing_here_message Nothing to show here
  15 +
app/views/issues/_show.html.haml
1 %li.wll{ :id => dom_id(issue), :class => issue_css_classes(issue), :url => project_issue_path(issue.project, issue) } 1 %li.wll{ :id => dom_id(issue), :class => issue_css_classes(issue), :url => project_issue_path(issue.project, issue) }
2 - .list_legend  
3 - .icon 2 + .issue_check
  3 + = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, :class => "selected_issue", :disabled => !can?(current_user, :modify_issue, issue)
4 .right 4 .right
5 - issue.labels.each do |label| 5 - issue.labels.each do |label|
6 %span.label.label-issue.grouped 6 %span.label.label-issue.grouped
@@ -34,4 +34,4 @@ @@ -34,4 +34,4 @@
34 &nbsp; 34 &nbsp;
35 35
36 - if issue.upvotes > 0 36 - if issue.upvotes > 0
37 - %span.badge.badge-success= "+#{issue.upvotes}"  
38 \ No newline at end of file 37 \ No newline at end of file
  38 + %span.badge.badge-success= "+#{issue.upvotes}"
app/views/issues/index.html.haml
@@ -14,35 +14,51 @@ @@ -14,35 +14,51 @@
14 = search_field_tag :issue_search, nil, { :placeholder => 'Search', :class => 'issue_search span3 right neib' } 14 = search_field_tag :issue_search, nil, { :placeholder => 'Search', :class => 'issue_search span3 right neib' }
15 15
16 .clearfix 16 .clearfix
  17 +
17 %div#issues-table-holder.ui-box 18 %div#issues-table-holder.ui-box
18 .title 19 .title
19 - .left  
20 - %ul.nav.nav-pills.left  
21 - %li{:class => ("active" if (params[:f] == issues_filter[:open] || !params[:f]))}  
22 - = link_to project_issues_path(@project, :f => issues_filter[:open], :milestone_id => params[:milestone_id]) do  
23 - Open  
24 - %li{:class => ("active" if params[:f] == issues_filter[:closed])}  
25 - = link_to project_issues_path(@project, :f => issues_filter[:closed], :milestone_id => params[:milestone_id]) do  
26 - Closed  
27 - %li{:class => ("active" if params[:f] == issues_filter[:to_me])}  
28 - = link_to project_issues_path(@project, :f => issues_filter[:to_me], :milestone_id => params[:milestone_id]) do  
29 - To Me  
30 - %li{:class => ("active" if params[:f] == issues_filter[:all])}  
31 - = link_to project_issues_path(@project, :f => issues_filter[:all], :milestone_id => params[:milestone_id]) do  
32 - All 20 + = check_box_tag "check_all_issues", nil, false, :class => "check_all_issues left"
  21 +
33 22
34 - .right  
35 - = form_tag project_issues_path(@project), :method => :get, :class => :right do  
36 - = select_tag(:label_name, options_for_select(issue_tags, params[:label_name]), :prompt => "Labels")  
37 - = select_tag(:assignee_id, options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), :prompt => "Assignee")  
38 - = select_tag(:milestone_id, options_from_collection_for_select(@project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), :prompt => "Milestone") 23 + .issues_bulk_update.hide
  24 + = form_tag bulk_update_project_issues_path(@project), :method => :post do
  25 + %span Update selected issues with
  26 + &nbsp;
  27 + = select_tag('update[status]', options_for_select(['open', 'closed']), :prompt => "Status")
  28 + = select_tag('update[assignee_id]', options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), :prompt => "Assignee")
  29 + = select_tag('update[milestone_id]', options_from_collection_for_select(@project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), :prompt => "Milestone")
  30 + = hidden_field_tag 'update[issues_ids]', []
39 = hidden_field_tag :f, params[:f] 31 = hidden_field_tag :f, params[:f]
40 - .clearfix 32 + = button_tag "Save", :class => "btn update_selected_issues"
  33 + .issues_filters
  34 + .left
  35 + %ul.nav.nav-pills.left
  36 + %li{:class => ("active" if (params[:f] == issues_filter[:open] || !params[:f]))}
  37 + = link_to project_issues_path(@project, :f => issues_filter[:open], :milestone_id => params[:milestone_id]) do
  38 + Open
  39 + %li{:class => ("active" if params[:f] == issues_filter[:closed])}
  40 + = link_to project_issues_path(@project, :f => issues_filter[:closed], :milestone_id => params[:milestone_id]) do
  41 + Closed
  42 + %li{:class => ("active" if params[:f] == issues_filter[:to_me])}
  43 + = link_to project_issues_path(@project, :f => issues_filter[:to_me], :milestone_id => params[:milestone_id]) do
  44 + To Me
  45 + %li{:class => ("active" if params[:f] == issues_filter[:all])}
  46 + = link_to project_issues_path(@project, :f => issues_filter[:all], :milestone_id => params[:milestone_id]) do
  47 + All
  48 +
  49 + .right
  50 + = form_tag project_issues_path(@project), :method => :get, :class => :right do
  51 + = select_tag(:label_name, options_for_select(issue_tags, params[:label_name]), :prompt => "Labels")
  52 + = select_tag(:assignee_id, options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), :prompt => "Assignee")
  53 + = select_tag(:milestone_id, options_from_collection_for_select(@project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), :prompt => "Milestone")
  54 + = hidden_field_tag :f, params[:f]
  55 + .clearfix
41 56
42 %ul#issues-table.unstyled.issues_table 57 %ul#issues-table.unstyled.issues_table
43 = render "issues" 58 = render "issues"
44 59
  60 +
45 :javascript 61 :javascript
46 $(function(){ 62 $(function(){
47 issuesPage(); 63 issuesPage();
48 - })  
49 \ No newline at end of file 64 \ No newline at end of file
  65 + })
config/routes.rb
@@ -194,6 +194,7 @@ Gitlab::Application.routes.draw do @@ -194,6 +194,7 @@ Gitlab::Application.routes.draw do
194 resources :issues do 194 resources :issues do
195 collection do 195 collection do
196 post :sort 196 post :sort
  197 + post :bulk_update
197 get :search 198 get :search
198 end 199 end
199 end 200 end