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 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 56 * Focus search field by pressing 's' key
65 57 */
... ...
app/assets/javascripts/issues.js
... ... @@ -67,6 +67,10 @@ function initIssuesSearch() {
67 67 */
68 68 function issuesPage(){
69 69 initIssuesSearch();
  70 + $("#update_status").chosen();
  71 + $("#update_assignee_id").chosen();
  72 + $("#update_milestone_id").chosen();
  73 +
70 74 $("#label_name").chosen();
71 75 $("#assignee_id").chosen();
72 76 $("#milestone_id").chosen();
... ... @@ -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 30 .issue {
31 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 40 p {
34 41 padding-top:0;
35 42 padding-bottom:2px;
... ... @@ -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 4 def initialize(project, user, params)
5 5 @project, @current_user, @params = project, user, params.dup
6 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 19 end
8 20  
... ...
app/contexts/issues_bulk_update_context.rb 0 → 100644
... ... @@ -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 113 render :partial => 'issues'
114 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 121 protected
117 122  
118 123 def issue
... ...
app/views/issues/_issues.html.haml
... ... @@ -12,3 +12,4 @@
12 12 - else
13 13 %li
14 14 %h4.nothing_here_message Nothing to show here
  15 +
... ...
app/views/issues/_show.html.haml
1 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 4 .right
5 5 - issue.labels.each do |label|
6 6 %span.label.label-issue.grouped
... ... @@ -34,4 +34,4 @@
34 34 &nbsp;
35 35  
36 36 - if issue.upvotes > 0
37   - %span.badge.badge-success= "+#{issue.upvotes}"
38 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 14 = search_field_tag :issue_search, nil, { :placeholder => 'Search', :class => 'issue_search span3 right neib' }
15 15  
16 16 .clearfix
  17 +
17 18 %div#issues-table-holder.ui-box
18 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 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 57 %ul#issues-table.unstyled.issues_table
43 58 = render "issues"
44 59  
  60 +
45 61 :javascript
46 62 $(function(){
47 63 issuesPage();
48   - })
49 64 \ No newline at end of file
  65 + })
... ...
config/routes.rb
... ... @@ -194,6 +194,7 @@ Gitlab::Application.routes.draw do
194 194 resources :issues do
195 195 collection do
196 196 post :sort
  197 + post :bulk_update
197 198 get :search
198 199 end
199 200 end
... ...