Project

General

Profile

Feature #4272 » dynamic-issue-columns.patch

Patch to trunk from r3084 - Eric Davis, 2009-11-23 20:43

View differences:

app/controllers/issues_controller.rb
22 22
  before_filter :find_issue, :only => [:show, :edit, :reply]
23 23
  before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
24 24
  before_filter :find_project, :only => [:new, :update_form, :preview]
25
  before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
25
  before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu, :update_column]
26 26
  before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
27 27
  accept_key_auth :index, :show, :changes
28 28

  
......
415 415
  end
416 416
  
417 417
  def context_menu
418
    @issues = Issue.find_all_by_id(params[:ids], :include => :project)
419
    if (@issues.size == 1)
420
      @issue = @issues.first
421
      @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
418
    # Wants issue context menu
419
    if params[:header_context_menu].nil? && !params[:ids].blank?
420
      @issues = Issue.find_all_by_id(params[:ids], :include => :project)
421
      if (@issues.size == 1)
422
        @issue = @issues.first
423
        @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
424
      end
425
      projects = @issues.collect(&:project).compact.uniq
426
      @project = projects.first if projects.size == 1
427

  
428
      @can = {
429
        :edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
430
        :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
431
        :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
432
        :move => (@project && User.current.allowed_to?(:move_issues, @project)),
433
        :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
434
        :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
435
      }
436

  
437
      if @project
438
        @assignables = @project.assignable_users
439
        @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
440
      end
441

  
442
      @priorities = IssuePriority.all.reverse
443
      @statuses = IssueStatus.find(:all, :order => 'position')
444
      @back = params[:back_url] || request.env['HTTP_REFERER']
445
      
446
      render :layout => false
447
    else
448
      # Wants a header context menu
449
      find_project unless params[:project_id].blank?
450
      retrieve_query
451
      @back = params[:back_url] || request.env['HTTP_REFERER']
452
      
453
      render :action => 'header_context_menu', :layout => false
422 454
    end
423
    projects = @issues.collect(&:project).compact.uniq
424
    @project = projects.first if projects.size == 1
455
  end
456

  
457
  # Takes the params and stores the updated column information from the index
458
  def update_column
459
    find_project unless params[:project_id].blank?
460
    retrieve_query
425 461

  
426
    @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
427
            :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
428
            :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
429
            :move => (@project && User.current.allowed_to?(:move_issues, @project)),
430
            :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
431
            :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
432
            }
433
    if @project
434
      @assignables = @project.assignable_users
435
      @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
462
    respond_to do |format|
463
      format.html { redirect_to :action => 'index', :project_id => params[:project_id] }
464
      format.js {render(:update) {|page| page.redirect_to :action => 'index', :project_id => params[:project_id] }}
436 465
    end
437
    
438
    @priorities = IssuePriority.all.reverse
439
    @statuses = IssueStatus.find(:all, :order => 'position')
440
    @back = params[:back_url] || request.env['HTTP_REFERER']
441
    
442
    render :layout => false
443 466
  end
444 467

  
445 468
  def update_form
......
498 521
      cond << " OR project_id = #{@project.id}" if @project
499 522
      @query = Query.find(params[:query_id], :conditions => cond)
500 523
      @query.project = @project
501
      session[:query] = {:id => @query.id, :project_id => @query.project_id}
502 524
      sort_clear
525
      @query.column_names = session[:query][:column_names] unless session[:query].nil? || session[:query][:column_names].nil? || session[:query][:id] != @query.id
526
      
527
      # Update column list
528
      if params[:column].present?
529
        @query.column_names = update_columns_from_session(@query, params[:column].to_sym)
530
      end
531
      session[:query] = {:id => @query.id, :project_id => @query.project_id, :column_names => @query.column_names}
503 532
    else
504 533
      if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
505 534
        # Give it a name, required to be valid
506 535
        @query = Query.new(:name => "_")
507 536
        @query.project = @project
537
        @query.column_names = session[:query][:column_names] unless session[:query].nil? || session[:query][:column_names].nil? || session[:query][:id] != @query.id
508 538
        if params[:fields] and params[:fields].is_a? Array
509 539
          params[:fields].each do |field|
510 540
            @query.add_filter(field, params[:operators][field], params[:values][field])
......
514 544
            @query.add_short_filter(field, params[field]) if params[field]
515 545
          end
516 546
        end
547

  
548
        # Update column list
549
        if params[:column].present?
550
          columns = update_columns_from_session(@query, params[:column].to_sym)
551

  
552
          # Order columns based on the Query ordering
553
          columns = @query.available_columns.select {|c| columns.include?(c.name)}.collect(&:name)
554

  
555
          @query.column_names = columns
556
        end
557
        
517 558
        @query.group_by = params[:group_by]
518
        session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by}
559
        session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
519 560
      else
520 561
        @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
521
        @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by])
562
        @query.column_names = session[:query][:column_names] if session[:query][:id] && session[:query][:column_names]
563
        @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
522 564
        @query.project = @project
523 565
      end
524 566
    end
525 567
  end
568
  
569
  def update_columns_from_session(query, column_name)
570
    # Force to symbol
571
    column_symbol = column_name.to_sym
572
    
573
    columns = @query.columns.collect(&:name) # Current columns
574
    # Columns from session
575
    if session[:query] && session[:query][:column_names]
576
      # Set Union
577
      columns = columns | session[:query][:column_names]
578
    end
579
    
580
    # Delete if the update column exists, otherwise add it
581
    if columns.include?(column_symbol)
582
      columns.delete(column_symbol)
583
    else
584
      columns << column_symbol
585
    end
586
    
587
    return columns
588
  end
526 589
end
app/controllers/queries_controller.rb
27 27
    @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
28 28
    @query.column_names = nil if params[:default_columns]
29 29
    
30
    # Use the columns from the session if they are set
31
    if @query.column_names.nil? && session[:query] && session[:query][:column_names]
32
      @query.column_names = session[:query][:column_names]
33
    end
34

  
30 35
    params[:fields].each do |field|
31 36
      @query.add_filter(field, params[:operators][field], params[:values][field])
32 37
    end if params[:fields]
......
52 57
      @query.column_names = nil if params[:default_columns]
53 58
      
54 59
      if @query.save
60
        # Clear the session[:query] to use the saved query
61
        session[:query] = { :id => @query.id, :project_id => @query.project_id, :column_names => @query.column_names}
55 62
        flash[:notice] = l(:notice_successful_update)
56 63
        redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
57 64
      end
app/views/issues/_list.rhtml
1 1
<% form_tag({}) do -%>	
2 2
<%= hidden_field_tag 'back_url', url_for(params) %>
3 3
<table class="list issues">
4
    <thead><tr>
5
        <th><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;',
4
    <thead><tr id="issue-header" class="no-select hascontextmenu">
5
      <th><%= check_box_tag("header_context_menu", false, false, :style => 'display: none;', :class => 'no-select' ) %>
6
          <%= hidden_field_tag("query_id", @query.id) if @query %>
7
          <%= hidden_field_tag("project_id", @project.id) if @project %>
8
          <%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(this.up("form")); return false;',
6 9
                                                           :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
7 10
        </th>
8 11
		<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
app/views/issues/header_context_menu.rhtml
1
<ul>
2
  <% @query.available_columns.each do |column| %>
3
  <li>
4
    <%= link_to_remote(column.caption,
5
                       {
6
                        :url => {
7
                          :controller => 'issues',
8
                          :action => 'update_column',
9
                          :project_id => @project,
10
                          :set_filter => true,
11
                          :column => column.name,
12
                          :query_id => @query.id
13
                        },
14
                        :with => "Form.serialize('query_form')"
15
                       },
16
                       {
17
                        :class => @query.columns.include?(column) ? 'icon-checked' : 'icon-none',
18
                        :method => :post
19
                       })%>
20
  </li>
21
  <% end %>
22
</ul>
public/javascripts/context_menu.js
34 34
		var tr = Event.findElement(e, 'tr');
35 35
		if (tr == document || tr == undefined  || !tr.hasClassName('hascontextmenu')) { return; }
36 36
		Event.stop(e);
37
		if (!this.isSelected(tr)) {
37
    	        if (!this.isSelected(tr)) {
38 38
			this.unselectAll();
39 39
			this.addSelection(tr);
40 40
			this.lastSelected = tr;
......
48 48
    if (window.opera && e.altKey) {	return; }
49 49
    if (Event.isLeftClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) {      
50 50
      var tr = Event.findElement(e, 'tr');
51
      if (tr!=null && tr!=document && tr.hasClassName('hascontextmenu')) {
51
        if (tr!=null && tr!=document && tr.hasClassName('hascontextmenu') && !tr.hasClassName('no-select')) {
52 52
        // a row was clicked, check if the click was on checkbox
53 53
        var box = Event.findElement(e, 'input');
54 54
        if (box!=document && box!=undefined) {
......
155 155
  },
156 156
  
157 157
  addSelection: function(tr) {
158
    tr.addClassName('context-menu-selection');
158
    if (!tr.hasClassName('no-select')) {
159
      tr.addClassName('context-menu-selection');
160
    }
159 161
    this.checkSelectionBox(tr, true);
160 162
  },
161 163
  
......
197 199
		if (all_checked) {
198 200
			boxes[i].checked = false;
199 201
			boxes[i].up('tr').removeClassName('context-menu-selection');
200
		} else if (boxes[i].checked == false) {
202
		} else if (boxes[i].checked == false && !boxes[i].hasClassName('no-select')) {
201 203
			boxes[i].checked = true;
202 204
			boxes[i].up('tr').addClassName('context-menu-selection');
203 205
		}
test/functional/issues_controller_test.rb
1093 1093
                            :attributes => { :href => '#',
1094 1094
                                             :class => 'icon-del disabled' }
1095 1095
  end
1096
  
1096

  
1097
  def test_context_menu_headers
1098
    @request.session[:user_id] = 2
1099
    get :context_menu, :header_context_menu => '1'
1100
    assert_response :success
1101
    assert_template 'header_context_menu'
1102

  
1103
    assert_select 'ul' do
1104
      assert_select 'a', 'Project'
1105
      assert_select 'a', 'Tracker'
1106
    end
1107
  end
1108

  
1109
  def test_update_column_to_add_a_column
1110
    @request.session[:user_id] = 2
1111
    post :update_column, :project_id => 'ecookbook', :column => 'author'
1112
    assert_redirected_to :action =>'index', :project_id => 'ecookbook'
1113

  
1114
    assert session[:query]
1115
    assert session[:query][:column_names]
1116
    assert session[:query][:column_names].include?(:author)
1117
  end
1118

  
1119
  def test_update_column_to_remove_a_column
1120
    @request.session[:user_id] = 2
1121
    post :update_column, :project_id => 'ecookbook', :column => 'status'
1122
    assert_redirected_to :action =>'index', :project_id => 'ecookbook'
1123

  
1124
    assert session[:query]
1125
    assert session[:query][:column_names]
1126
    assert !session[:query][:column_names].include?(:status)
1127
  end
1128

  
1097 1129
  def test_destroy_routing
1098 1130
    assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
1099 1131
      {:controller => 'issues', :action => 'destroy', :id => '1'},
test/functional/queries_controller_test.rb
58 58
                                                 :disabled => nil }
59 59
  end
60 60
  
61
  def test_get_new_project_query_with_columns_from_session
62
    @request.session[:user_id] = 2
63
    @request.session[:query] = {:column_names => ['tracker', 'subject']}
64
    get :new, :project_id => 1
65
    assert_response :success
66
    assert_template 'new'
67

  
68
    assert_select '#query_default_columns[checked=checked]', false
69

  
70
    assert_select 'select#selected_columns' do
71
      assert_select 'option[value=tracker]'
72
      assert_select 'option[value=subject]'
73
    end
74
  end
75

  
61 76
  def test_new_project_public_query
62 77
    @request.session[:user_id] = 2
63 78
    post :new,
(1-1/5)