Feature #4272 » dynamic-issue-columns.patch
| 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, |