Issue list now supports bulk edit/move/delete (#563, #607). For now, issues from different projects can not be bulk edited/moved/deleted at once.
There are 2 ways to select a set of issues on the issue list: * by using checkbox and/or the little pencil that will select/unselect all issues (#567) * by clicking on the rows (but not on the links), Ctrl and Shift keys can be used to select multiple issues Context menu was disabled on links so that the default context menu of the browser is displayed when right-clicking on a link (#545). All this was tested with Firefox 2, IE 6/7, Opera 8 (use Alt+Click instead of Right-click) and Safari 2/3. git-svn-id: http://redmine.rubyforge.org/svn/trunk@1130 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
43a6f312ed
commit
4155c97222
|
@ -19,13 +19,14 @@ class IssuesController < ApplicationController
|
|||
layout 'base'
|
||||
menu_item :new_issue, :only => :new
|
||||
|
||||
before_filter :find_issue, :except => [:index, :changes, :preview, :new, :update_form]
|
||||
before_filter :find_issue, :only => [:show, :edit, :destroy_attachment]
|
||||
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
|
||||
before_filter :find_project, :only => [:new, :update_form]
|
||||
before_filter :authorize, :except => [:index, :changes, :preview, :update_form]
|
||||
before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu]
|
||||
before_filter :find_optional_project, :only => [:index, :changes]
|
||||
accept_key_auth :index, :changes
|
||||
|
||||
cache_sweeper :issue_sweeper, :only => [ :new, :edit, :destroy ]
|
||||
cache_sweeper :issue_sweeper, :only => [ :new, :edit, :bulk_edit, :destroy ]
|
||||
|
||||
helper :journals
|
||||
helper :projects
|
||||
|
@ -152,18 +153,20 @@ class IssuesController < ApplicationController
|
|||
@priorities = Enumeration::get_values('IPRI')
|
||||
@custom_values = []
|
||||
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
||||
|
||||
@notes = params[:notes]
|
||||
journal = @issue.init_journal(User.current, @notes)
|
||||
# User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
|
||||
if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
|
||||
attrs = params[:issue].dup
|
||||
attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
|
||||
attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
|
||||
@issue.attributes = attrs
|
||||
end
|
||||
|
||||
if request.get?
|
||||
@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
|
||||
else
|
||||
@notes = params[:notes]
|
||||
journal = @issue.init_journal(User.current, @notes)
|
||||
# User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
|
||||
if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
|
||||
attrs = params[:issue].dup
|
||||
attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
|
||||
attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
|
||||
@issue.attributes = attrs
|
||||
end
|
||||
# Update custom fields if user has :edit permission
|
||||
if @edit_allowed && params[:custom_fields]
|
||||
@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
|
||||
|
@ -191,8 +194,78 @@ class IssuesController < ApplicationController
|
|||
flash.now[:error] = l(:notice_locking_conflict)
|
||||
end
|
||||
|
||||
# Bulk edit a set of issues
|
||||
def bulk_edit
|
||||
if request.post?
|
||||
status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
|
||||
priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
|
||||
assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
|
||||
category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
|
||||
fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
|
||||
|
||||
unsaved_issue_ids = []
|
||||
@issues.each do |issue|
|
||||
journal = issue.init_journal(User.current, params[:notes])
|
||||
issue.priority = priority if priority
|
||||
issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
|
||||
issue.category = category if category
|
||||
issue.fixed_version = fixed_version if fixed_version
|
||||
issue.start_date = params[:start_date] unless params[:start_date].blank?
|
||||
issue.due_date = params[:due_date] unless params[:due_date].blank?
|
||||
issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
|
||||
# Don't save any change to the issue if the user is not authorized to apply the requested status
|
||||
if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
|
||||
# Send notification for each issue (if changed)
|
||||
Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
|
||||
else
|
||||
# Keep unsaved issue ids to display them in flash error
|
||||
unsaved_issue_ids << issue.id
|
||||
end
|
||||
end
|
||||
if unsaved_issue_ids.empty?
|
||||
flash[:notice] = l(:notice_successful_update) unless @issues.empty?
|
||||
else
|
||||
flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
|
||||
end
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
|
||||
return
|
||||
end
|
||||
# Find potential statuses the user could be allowed to switch issues to
|
||||
@available_statuses = Workflow.find(:all, :include => :new_status,
|
||||
:conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
|
||||
end
|
||||
|
||||
def move
|
||||
@allowed_projects = []
|
||||
# find projects to which the user is allowed to move the issue
|
||||
if User.current.admin?
|
||||
# admin is allowed to move issues to any active (visible) project
|
||||
@allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
|
||||
else
|
||||
User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
|
||||
end
|
||||
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
|
||||
@target_project ||= @project
|
||||
@trackers = @target_project.trackers
|
||||
if request.post?
|
||||
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
|
||||
unsaved_issue_ids = []
|
||||
@issues.each do |issue|
|
||||
unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
|
||||
end
|
||||
if unsaved_issue_ids.empty?
|
||||
flash[:notice] = l(:notice_successful_update) unless @issues.empty?
|
||||
else
|
||||
flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
|
||||
end
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
|
||||
return
|
||||
end
|
||||
render :layout => false if request.xhr?
|
||||
end
|
||||
|
||||
def destroy
|
||||
@issue.destroy
|
||||
@issues.each(&:destroy)
|
||||
redirect_to :action => 'index', :project_id => @project
|
||||
end
|
||||
|
||||
|
@ -208,17 +281,27 @@ class IssuesController < ApplicationController
|
|||
end
|
||||
|
||||
def context_menu
|
||||
@issues = Issue.find_all_by_id(params[:ids], :include => :project)
|
||||
if (@issues.size == 1)
|
||||
@issue = @issues.first
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
@assignables = @issue.assignable_users
|
||||
@assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
|
||||
end
|
||||
projects = @issues.collect(&:project).compact.uniq
|
||||
@project = projects.first if projects.size == 1
|
||||
|
||||
@can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
|
||||
:update => (@issue && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && !@allowed_statuses.empty?))),
|
||||
:move => (@project && User.current.allowed_to?(:move_issues, @project)),
|
||||
:copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
|
||||
:delete => (@project && User.current.allowed_to?(:delete_issues, @project))
|
||||
}
|
||||
|
||||
@priorities = Enumeration.get_values('IPRI').reverse
|
||||
@statuses = IssueStatus.find(:all, :order => 'position')
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
@assignables = @issue.assignable_users
|
||||
@assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
|
||||
@can = {:edit => User.current.allowed_to?(:edit_issues, @project),
|
||||
:assign => (@allowed_statuses.any? || User.current.allowed_to?(:edit_issues, @project)),
|
||||
:add => User.current.allowed_to?(:add_issues, @project),
|
||||
:move => User.current.allowed_to?(:move_issues, @project),
|
||||
:copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
|
||||
:delete => User.current.allowed_to?(:delete_issues, @project)}
|
||||
@back = request.env['HTTP_REFERER']
|
||||
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
|
@ -242,6 +325,21 @@ private
|
|||
render_404
|
||||
end
|
||||
|
||||
# Filter for bulk operations
|
||||
def find_issues
|
||||
@issues = Issue.find_all_by_id(params[:id] || params[:ids])
|
||||
raise ActiveRecord::RecordNotFound if @issues.empty?
|
||||
projects = @issues.collect(&:project).compact.uniq
|
||||
if projects.size == 1
|
||||
@project = projects.first
|
||||
else
|
||||
# TODO: let users bulk edit/move/destroy issues from different projects
|
||||
render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_project
|
||||
@project = Project.find(params[:project_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
|
|
|
@ -22,7 +22,7 @@ class ProjectsController < ApplicationController
|
|||
menu_item :roadmap, :only => :roadmap
|
||||
menu_item :files, :only => [:list_files, :add_file]
|
||||
menu_item :settings, :only => :settings
|
||||
menu_item :issues, :only => [:bulk_edit_issues, :changelog, :move_issues]
|
||||
menu_item :issues, :only => [:changelog]
|
||||
|
||||
before_filter :find_project, :except => [ :index, :list, :add ]
|
||||
before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
|
||||
|
@ -182,83 +182,6 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# Bulk edit issues
|
||||
def bulk_edit_issues
|
||||
if request.post?
|
||||
status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
|
||||
priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
|
||||
assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
|
||||
category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
|
||||
fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
|
||||
issues = @project.issues.find_all_by_id(params[:issue_ids])
|
||||
unsaved_issue_ids = []
|
||||
issues.each do |issue|
|
||||
journal = issue.init_journal(User.current, params[:notes])
|
||||
issue.priority = priority if priority
|
||||
issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
|
||||
issue.category = category if category
|
||||
issue.fixed_version = fixed_version if fixed_version
|
||||
issue.start_date = params[:start_date] unless params[:start_date].blank?
|
||||
issue.due_date = params[:due_date] unless params[:due_date].blank?
|
||||
issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
|
||||
# Don't save any change to the issue if the user is not authorized to apply the requested status
|
||||
if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
|
||||
# Send notification for each issue (if changed)
|
||||
Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
|
||||
else
|
||||
# Keep unsaved issue ids to display them in flash error
|
||||
unsaved_issue_ids << issue.id
|
||||
end
|
||||
end
|
||||
if unsaved_issue_ids.empty?
|
||||
flash[:notice] = l(:notice_successful_update) unless issues.empty?
|
||||
else
|
||||
flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
|
||||
end
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
|
||||
return
|
||||
end
|
||||
# Find potential statuses the user could be allowed to switch issues to
|
||||
@available_statuses = Workflow.find(:all, :include => :new_status,
|
||||
:conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
|
||||
render :update do |page|
|
||||
page.hide 'query_form'
|
||||
page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
|
||||
end
|
||||
end
|
||||
|
||||
def move_issues
|
||||
@issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project and return unless @issues
|
||||
|
||||
@projects = []
|
||||
# find projects to which the user is allowed to move the issue
|
||||
if User.current.admin?
|
||||
# admin is allowed to move issues to any active (visible) project
|
||||
@projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
|
||||
else
|
||||
User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:move_issues)}
|
||||
end
|
||||
@target_project = @projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
|
||||
@target_project ||= @project
|
||||
@trackers = @target_project.trackers
|
||||
if request.post?
|
||||
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
|
||||
unsaved_issue_ids = []
|
||||
@issues.each do |issue|
|
||||
unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
|
||||
end
|
||||
if unsaved_issue_ids.empty?
|
||||
flash[:notice] = l(:notice_successful_update) unless @issues.empty?
|
||||
else
|
||||
flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
|
||||
end
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
|
||||
return
|
||||
end
|
||||
render :layout => false if request.xhr?
|
||||
end
|
||||
|
||||
def add_file
|
||||
if request.post?
|
||||
@version = @project.versions.find_by_id(params[:version_id])
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
<div id="bulk-edit"></div>
|
||||
<table class="list">
|
||||
<% form_tag({}) do -%>
|
||||
<table class="list issues">
|
||||
<thead><tr>
|
||||
<th><%= link_to_remote(image_tag('edit.png'),
|
||||
{:url => { :controller => 'projects', :action => 'bulk_edit_issues', :id => @project },
|
||||
:method => :get},
|
||||
{:title => l(:label_bulk_edit_selected_issues)}) if @project && User.current.allowed_to?(:edit_issues, @project) %>
|
||||
<th><%= link_to image_tag('edit.png'), {}, :onclick => 'toggleIssuesSelection(this.up("form")); return false;' %>
|
||||
</th>
|
||||
<%= sort_header_tag("#{Issue.table_name}.id", :caption => '#', :default_order => 'desc') %>
|
||||
<% query.columns.each do |column| %>
|
||||
|
@ -12,14 +9,21 @@
|
|||
<% end %>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<% issues.each do |issue| %>
|
||||
<% issues.each do |issue| -%>
|
||||
<tr id="issue-<%= issue.id %>" class="issue hascontextmenu <%= cycle('odd', 'even') %> <%= "status-#{issue.status.position} priority-#{issue.priority.position}" %>">
|
||||
<td class="checkbox"><%= check_box_tag("issue_ids[]", issue.id, false, :id => "issue_#{issue.id}", :disabled => (!@project || @project != issue.project)) %></td>
|
||||
<td class="checkbox"><%= check_box_tag("ids[]", issue.id, false) %></td>
|
||||
<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
|
||||
<% query.columns.each do |column| %>
|
||||
<%= content_tag 'td', column_content(column, issue), :class => column.name %>
|
||||
<% end %>
|
||||
<% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end -%>
|
||||
</tbody>
|
||||
</table>
|
||||
<% end -%>
|
||||
|
||||
<% content_for :header_tags do -%>
|
||||
<%= javascript_include_tag 'context_menu' %>
|
||||
<%= stylesheet_link_tag 'context_menu' %>
|
||||
<% end -%>
|
||||
|
||||
<div id="context-menu" style="display: none;"></div>
|
||||
<%= javascript_tag "new ContextMenu('#{url_for(:controller => 'issues', :action => 'context_menu')}')" %>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<% if issues.length > 0 %>
|
||||
<table class="list">
|
||||
<% if issues && issues.any? %>
|
||||
<% form_tag({}) do %>
|
||||
<table class="list issues">
|
||||
<thead><tr>
|
||||
<th>#</th>
|
||||
<th><%=l(:field_tracker)%></th>
|
||||
|
@ -9,6 +10,7 @@
|
|||
<% for issue in issues %>
|
||||
<tr id="issue-<%= issue.id %>" class="issue hascontextmenu <%= cycle('odd', 'even') %> <%= "status-#{issue.status.position} priority-#{issue.priority.position}" %>">
|
||||
<td class="id">
|
||||
<%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;') %>
|
||||
<%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>
|
||||
</td>
|
||||
<td><%=h issue.project.name %> - <%= issue.tracker.name %><br />
|
||||
|
@ -20,6 +22,7 @@
|
|||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<i><%=l(:label_no_data)%></i>
|
||||
<% end %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% end %>
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
<div id="bulk-edit-fields">
|
||||
<fieldset class="box"><legend><%= l(:label_bulk_edit_selected_issues) %></legend>
|
||||
<h2><%= l(:label_bulk_edit_selected_issues) %></h2>
|
||||
|
||||
<ul><%= @issues.collect {|i| content_tag('li', link_to(h("#{i.tracker} ##{i.id}"), { :action => 'show', :id => i }) + h(": #{i.subject}")) }.join("\n") %></ul>
|
||||
|
||||
<% form_tag() do %>
|
||||
<%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
|
||||
<div class="box">
|
||||
<fieldset>
|
||||
<legend><%= l(:label_change_properties) %></legend>
|
||||
<p>
|
||||
<% if @available_statuses.any? %>
|
||||
<label><%= l(:field_status) %>:
|
||||
|
@ -28,11 +34,12 @@
|
|||
<label><%= l(:field_done_ratio) %>:
|
||||
<%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
|
||||
</p>
|
||||
|
||||
<label for="notes"><%= l(:field_notes) %></label><br />
|
||||
<%= text_area_tag 'notes', '', :cols => 80, :rows => 5 %>
|
||||
|
||||
</fieldset>
|
||||
<p><%= submit_tag l(:button_apply) %>
|
||||
<%= link_to l(:button_cancel), {}, :onclick => 'Element.hide("bulk-edit-fields"); if ($("query_form")) {Element.show("query_form")}; return false;' %></p>
|
||||
|
||||
<fieldset><legend><%= l(:field_notes) %></legend>
|
||||
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
|
||||
<%= wikitoolbar_for 'notes' %>
|
||||
</div>
|
||||
|
||||
<p><%= submit_tag l(:button_submit) %>
|
||||
<% end %>
|
|
@ -1,40 +1,45 @@
|
|||
<% back_to = url_for(:controller => 'issues', :action => 'index', :project_id => @project) %>
|
||||
<ul>
|
||||
<% if !@issue.nil? -%>
|
||||
<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
|
||||
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
|
||||
<ul>
|
||||
<% @statuses.each do |s| %>
|
||||
<% @statuses.each do |s| -%>
|
||||
<li><%= context_menu_link s.name, {:controller => 'issues', :action => 'edit', :id => @issue, :issue => {:status_id => s}},
|
||||
:selected => (s == @issue.status), :disabled => !(@allowed_statuses.include?(s)) %></li>
|
||||
<% end %>
|
||||
:selected => (s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li>
|
||||
<% end -%>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu"><%= l(:field_priority) %></a>
|
||||
<ul>
|
||||
<% @priorities.each do |p| %>
|
||||
<li><%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => back_to}, :method => :post,
|
||||
<% @priorities.each do |p| -%>
|
||||
<li><%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => @back}, :method => :post,
|
||||
:selected => (p == @issue.priority), :disabled => !@can[:edit] %></li>
|
||||
<% end %>
|
||||
<% end -%>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
|
||||
<ul>
|
||||
<% @assignables.each do |u| %>
|
||||
<li><%= context_menu_link u.name, {:controller => 'issues', :action => 'edit', :id => @issue, :issue => {:assigned_to_id => u}, :back_to => back_to}, :method => :post,
|
||||
:selected => (u == @issue.assigned_to), :disabled => !@can[:assign] %></li>
|
||||
<% end %>
|
||||
<li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, :issue => {:assigned_to_id => nil}, :back_to => back_to}, :method => :post,
|
||||
:selected => @issue.assigned_to.nil?, :disabled => !@can[:assign] %></li>
|
||||
<% @assignables.each do |u| -%>
|
||||
<li><%= context_menu_link u.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => u, :back_to => @back}, :method => :post,
|
||||
:selected => (u == @issue.assigned_to), :disabled => !@can[:update] %></li>
|
||||
<% end -%>
|
||||
<li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => '', :back_to => @back}, :method => :post,
|
||||
:selected => @issue.assigned_to.nil?, :disabled => !@can[:update] %></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
|
||||
:class => 'icon-copy', :disabled => !@can[:copy] %></li>
|
||||
<li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
|
||||
:class => 'icon-move', :disabled => !@can[:move] %>
|
||||
<li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
|
||||
:method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
|
||||
<% else -%>
|
||||
<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
|
||||
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
|
||||
<% end -%>
|
||||
|
||||
<li><%= context_menu_link l(:button_move), {:controller => 'issues', :action => 'move', :ids => @issues.collect(&:id)},
|
||||
:class => 'icon-move', :disabled => !@can[:move] %></li>
|
||||
<li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id)},
|
||||
:method => :post, :confirm => l(:text_issues_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %></li>
|
||||
</ul>
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
<%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<h2><%=h @query.name %></h2>
|
||||
<div id="query_form"></div>
|
||||
<% html_title @query.name %>
|
||||
|
@ -41,7 +40,6 @@
|
|||
<% if @issues.empty? %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% else %>
|
||||
<% form_tag({:controller => 'projects', :action => 'bulk_edit_issues', :id => @project}, :id => 'issues_form', :onsubmit => "if (!checkBulkEdit(this)) {alert('#{l(:notice_no_issue_selected)}'); return false;}" ) do %>
|
||||
<%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
|
||||
<div class="contextual">
|
||||
<%= l(:label_export_to) %>
|
||||
|
@ -51,7 +49,6 @@
|
|||
<p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
<%= render :partial => 'issues/sidebar' %>
|
||||
|
@ -60,13 +57,4 @@
|
|||
<% content_for :header_tags do %>
|
||||
<%= auto_discovery_link_tag(:atom, {:query_id => @query, :format => 'atom', :page => nil, :key => User.current.rss_key}, :title => l(:label_issue_plural)) %>
|
||||
<%= auto_discovery_link_tag(:atom, {:action => 'changes', :query_id => @query, :format => 'atom', :page => nil, :key => User.current.rss_key}, :title => l(:label_changes_details)) %>
|
||||
<%= javascript_include_tag 'calendar/calendar' %>
|
||||
<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
|
||||
<%= javascript_include_tag 'calendar/calendar-setup' %>
|
||||
<%= stylesheet_link_tag 'calendar' %>
|
||||
<%= javascript_include_tag 'context_menu' %>
|
||||
<%= stylesheet_link_tag 'context_menu' %>
|
||||
<% end %>
|
||||
|
||||
<div id="context-menu" style="display: none;"></div>
|
||||
<%= javascript_tag 'new ContextMenu({})' %>
|
||||
|
|
|
@ -1,23 +1,15 @@
|
|||
<h2><%=l(:button_move)%></h2>
|
||||
<h2><%= l(:button_move) %></h2>
|
||||
|
||||
<ul><%= @issues.collect {|i| content_tag('li', link_to(h("#{i.tracker} ##{i.id}"), { :action => 'show', :id => i }) + h(": #{i.subject}")) }.join("\n") %></ul>
|
||||
|
||||
<% form_tag({:action => 'move_issues', :id => @project}, :class => 'tabular', :id => 'move_form') do %>
|
||||
<% form_tag({}, :id => 'move_form') do %>
|
||||
<%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
|
||||
|
||||
<div class="box">
|
||||
<p><label><%= l(:label_issue_plural) %> :</label>
|
||||
<% for issue in @issues %>
|
||||
<%= link_to_issue issue %>: <%=h issue.subject %>
|
||||
<%= hidden_field_tag "issue_ids[]", issue.id %><br />
|
||||
<% end %>
|
||||
<i>(<%= @issues.length%> <%= lwr(:label_issue, @issues.length)%>)</i></p>
|
||||
|
||||
|
||||
|
||||
<!--[form:issue]-->
|
||||
<div class="box tabular">
|
||||
<p><label for="new_project_id"><%=l(:field_project)%> :</label>
|
||||
<%= select_tag "new_project_id",
|
||||
options_from_collection_for_select(@projects, 'id', 'name', @target_project.id),
|
||||
:onchange => remote_function(:url => {:action => 'move_issues' , :id => @project},
|
||||
options_from_collection_for_select(@allowed_projects, 'id', 'name', @target_project.id),
|
||||
:onchange => remote_function(:url => {:action => 'move' , :id => @project},
|
||||
:method => :get,
|
||||
:update => 'content',
|
||||
:with => "Form.serialize('move_form')") %></p>
|
||||
|
@ -25,5 +17,6 @@
|
|||
<p><label for="new_tracker_id"><%=l(:field_tracker)%> :</label>
|
||||
<%= select_tag "new_tracker_id", "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@trackers, "id", "name") %></p>
|
||||
</div>
|
||||
|
||||
<%= submit_tag l(:button_move) %>
|
||||
<% end %>
|
|
@ -3,7 +3,7 @@
|
|||
<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
|
||||
<%= watcher_tag(@issue, User.current) %>
|
||||
<%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
|
||||
<%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
|
||||
<%= link_to_if_authorized l(:button_move), {:controller => 'issues', :action => 'move', :id => @issue }, :class => 'icon icon-move' %>
|
||||
<%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -37,6 +37,6 @@
|
|||
<% end %>
|
||||
|
||||
<div id="context-menu" style="display: none;"></div>
|
||||
<%= javascript_tag 'new ContextMenu({})' %>
|
||||
<%= javascript_tag "new ContextMenu('#{url_for(:controller => 'issues', :action => 'context_menu')}')" %>
|
||||
|
||||
<% html_title(l(:label_my_page)) -%>
|
||||
|
|
|
@ -568,3 +568,4 @@ label_associated_revisions: Асоциирани ревизии
|
|||
setting_user_format: Потребителски формат
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -568,3 +568,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -568,3 +568,4 @@ enumeration_doc_categories: Dokumentenkategorien
|
|||
enumeration_activities: Aktivitäten (Zeiterfassung)
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -542,6 +542,7 @@ text_user_mail_option: "For unselected projects, you will only receive notificat
|
|||
text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
|
||||
text_load_default_configuration: Load the default configuration
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
||||
default_role_manager: Manager
|
||||
default_role_developper: Developer
|
||||
|
|
|
@ -571,3 +571,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -572,3 +572,4 @@ label_associated_revisions: Liittyvät versiot
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -543,6 +543,7 @@ text_user_mail_option: "Pour les projets non sélectionnés, vous recevrez seule
|
|||
text_no_configuration_data: "Les rôles, trackers, statuts et le workflow ne sont pas encore paramétrés.\nIl est vivement recommandé de charger le paramétrage par defaut. Vous pourrez le modifier une fois chargé."
|
||||
text_load_default_configuration: Charger le paramétrage par défaut
|
||||
text_status_changed_by_changeset: Appliqué par commit %s.
|
||||
text_issues_destroy_confirmation: 'Etes-vous sûr de vouloir supprimer le(s) demandes(s) selectionnée(s) ?'
|
||||
|
||||
default_role_manager: Manager
|
||||
default_role_developper: Développeur
|
||||
|
|
|
@ -568,3 +568,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -568,3 +568,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -569,3 +569,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -568,3 +568,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -569,3 +569,4 @@ label_associated_revisions: susijusios revizijos
|
|||
setting_user_format: Vartotojo atvaizdavimo formatas
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -569,3 +569,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -568,3 +568,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -568,3 +568,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -568,3 +568,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -568,3 +568,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -569,3 +569,4 @@ enumeration_doc_categories: Категории документов
|
|||
enumeration_activities: Действия (учет времени)
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -569,3 +569,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -569,3 +569,4 @@ label_associated_revisions: Associated revisions
|
|||
setting_user_format: Users display format
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -568,3 +568,4 @@ enumeration_doc_categories: 文件分類
|
|||
enumeration_activities: 活動 (time tracking)
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -571,3 +571,4 @@ label_associated_revisions: 相关的版本
|
|||
setting_user_format: 用户显示格式
|
||||
text_status_changed_by_changeset: Applied in changeset %s.
|
||||
label_more: More
|
||||
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
|
||||
|
|
|
@ -31,11 +31,10 @@ Redmine::AccessControl.map do |map|
|
|||
:queries => :index,
|
||||
:reports => :issue_report}, :public => true
|
||||
map.permission :add_issues, {:issues => :new}
|
||||
map.permission :edit_issues, {:projects => :bulk_edit_issues,
|
||||
:issues => [:edit, :destroy_attachment]}
|
||||
map.permission :edit_issues, {:issues => [:edit, :bulk_edit, :destroy_attachment]}
|
||||
map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
|
||||
map.permission :add_issue_notes, {:issues => :edit}
|
||||
map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
|
||||
map.permission :move_issues, {:issues => :move}, :require => :loggedin
|
||||
map.permission :delete_issues, {:issues => :destroy}, :require => :member
|
||||
# Queries
|
||||
map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/* redMine - project management software
|
||||
Copyright (C) 2006-2008 Jean-Philippe Lang */
|
||||
|
||||
function checkAll (id, checked) {
|
||||
var el = document.getElementById(id);
|
||||
for (var i = 0; i < el.elements.length; i++) {
|
||||
|
@ -49,16 +52,6 @@ function promptToRemote(text, param, url) {
|
|||
}
|
||||
}
|
||||
|
||||
/* checks that at least one checkbox is checked (used when submitting bulk edit form) */
|
||||
function checkBulkEdit(form) {
|
||||
for (var i = 0; i < form.elements.length; i++) {
|
||||
if (form.elements[i].checked) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function collapseScmEntry(id) {
|
||||
var els = document.getElementsByClassName(id, 'browser');
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
|
|
|
@ -1,47 +1,161 @@
|
|||
/* redMine - project management software
|
||||
Copyright (C) 2006-2008 Jean-Philippe Lang */
|
||||
|
||||
var observingContextMenuClick;
|
||||
|
||||
ContextMenu = Class.create();
|
||||
ContextMenu.prototype = {
|
||||
initialize: function (options) {
|
||||
this.options = Object.extend({selector: '.hascontextmenu'}, options || { });
|
||||
|
||||
Event.observe(document, 'click', function(e){
|
||||
var t = Event.findElement(e, 'a');
|
||||
if ((t != document) && (Element.hasClassName(t, 'disabled') || Element.hasClassName(t, 'submenu'))) {
|
||||
Event.stop(e);
|
||||
} else {
|
||||
$('context-menu').hide();
|
||||
if (this.selection) {
|
||||
this.selection.removeClassName('context-menu-selection');
|
||||
}
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
|
||||
$$(this.options.selector).invoke('observe', (window.opera ? 'click' : 'contextmenu'), function(e){
|
||||
if (window.opera && !e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
this.show(e);
|
||||
}.bind(this));
|
||||
|
||||
initialize: function (url) {
|
||||
this.url = url;
|
||||
|
||||
// prevent selection when using Ctrl/Shit key
|
||||
var tables = $$('table.issues');
|
||||
for (i=0; i<tables.length; i++) {
|
||||
tables[i].onselectstart = function () { return false; } // ie
|
||||
tables[i].onmousedown = function () { return false; } // mozilla
|
||||
}
|
||||
|
||||
if (!observingContextMenuClick) {
|
||||
Event.observe(document, 'click', this.Click.bindAsEventListener(this));
|
||||
Event.observe(document, (window.opera ? 'click' : 'contextmenu'), this.RightClick.bindAsEventListener(this));
|
||||
observingContextMenuClick = true;
|
||||
}
|
||||
|
||||
this.unselectAll();
|
||||
this.lastSelected = null;
|
||||
},
|
||||
show: function(e) {
|
||||
|
||||
RightClick: function(e) {
|
||||
this.hideMenu();
|
||||
// do not show the context menu on links
|
||||
if (Event.findElement(e, 'a') != document) { return; }
|
||||
// right-click simulated by Alt+Click with Opera
|
||||
if (window.opera && !e.altKey) { return; }
|
||||
var tr = Event.findElement(e, 'tr');
|
||||
if ((tr == document) || !tr.hasClassName('hascontextmenu')) { return; }
|
||||
Event.stop(e);
|
||||
Element.hide('context-menu');
|
||||
if (this.selection) {
|
||||
this.selection.removeClassName('context-menu-selection');
|
||||
if (!this.isSelected(tr)) {
|
||||
this.unselectAll();
|
||||
this.addSelection(tr);
|
||||
this.lastSelected = tr;
|
||||
}
|
||||
this.showMenu(e);
|
||||
},
|
||||
|
||||
Click: function(e) {
|
||||
this.hideMenu();
|
||||
if (Event.findElement(e, 'a') != document) { return; }
|
||||
if (window.opera && e.altKey) { return; }
|
||||
if (Event.isLeftClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) {
|
||||
var tr = Event.findElement(e, 'tr');
|
||||
if (tr!=document && tr.hasClassName('hascontextmenu')) {
|
||||
// a row was clicked, check if the click was on checkbox
|
||||
var box = Event.findElement(e, 'input');
|
||||
if (box!=document) {
|
||||
// a checkbox may be clicked
|
||||
if (box.checked) {
|
||||
tr.addClassName('context-menu-selection');
|
||||
} else {
|
||||
tr.removeClassName('context-menu-selection');
|
||||
}
|
||||
} else {
|
||||
if (e.ctrlKey) {
|
||||
this.toggleSelection(tr);
|
||||
} else if (e.shiftKey) {
|
||||
if (this.lastSelected != null) {
|
||||
var toggling = false;
|
||||
var rows = $$('.hascontextmenu');
|
||||
for (i=0; i<rows.length; i++) {
|
||||
if (toggling || rows[i]==tr) {
|
||||
this.addSelection(rows[i]);
|
||||
}
|
||||
if (rows[i]==tr || rows[i]==this.lastSelected) {
|
||||
toggling = !toggling;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.addSelection(tr);
|
||||
}
|
||||
} else {
|
||||
this.unselectAll();
|
||||
this.addSelection(tr);
|
||||
}
|
||||
this.lastSelected = tr;
|
||||
}
|
||||
} else {
|
||||
// click is outside the rows
|
||||
var t = Event.findElement(e, 'a');
|
||||
if ((t != document) && (Element.hasClassName(t, 'disabled') || Element.hasClassName(t, 'submenu'))) {
|
||||
Event.stop(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showMenu: function(e) {
|
||||
$('context-menu').style['left'] = (Event.pointerX(e) + 'px');
|
||||
$('context-menu').style['top'] = (Event.pointerY(e) + 'px');
|
||||
Element.update('context-menu', '');
|
||||
new Ajax.Updater({success:'context-menu'}, this.url,
|
||||
{asynchronous:true,
|
||||
evalScripts:true,
|
||||
parameters:Form.serialize(Event.findElement(e, 'form')),
|
||||
onComplete:function(request){
|
||||
Effect.Appear('context-menu', {duration: 0.20});
|
||||
if (window.parseStylesheets) { window.parseStylesheets(); } // IE
|
||||
}})
|
||||
},
|
||||
|
||||
hideMenu: function() {
|
||||
Element.hide('context-menu');
|
||||
},
|
||||
|
||||
addSelection: function(tr) {
|
||||
tr.addClassName('context-menu-selection');
|
||||
this.checkSelectionBox(tr, true);
|
||||
},
|
||||
|
||||
toggleSelection: function(tr) {
|
||||
if (this.isSelected(tr)) {
|
||||
this.removeSelection(tr);
|
||||
} else {
|
||||
this.addSelection(tr);
|
||||
}
|
||||
},
|
||||
|
||||
removeSelection: function(tr) {
|
||||
tr.removeClassName('context-menu-selection');
|
||||
this.checkSelectionBox(tr, false);
|
||||
},
|
||||
|
||||
unselectAll: function() {
|
||||
var rows = $$('.hascontextmenu');
|
||||
for (i=0; i<rows.length; i++) {
|
||||
this.removeSelection(rows[i]);
|
||||
}
|
||||
},
|
||||
|
||||
checkSelectionBox: function(tr, checked) {
|
||||
var inputs = Element.getElementsBySelector(tr, 'input');
|
||||
if (inputs.length > 0) { inputs[0].checked = checked; }
|
||||
},
|
||||
|
||||
isSelected: function(tr) {
|
||||
return Element.hasClassName(tr, 'context-menu-selection');
|
||||
}
|
||||
}
|
||||
|
||||
var tr = Event.findElement(e, 'tr');
|
||||
tr.addClassName('context-menu-selection');
|
||||
this.selection = tr;
|
||||
var id = tr.id.substring(6, tr.id.length);
|
||||
/* TODO: do not hard code path */
|
||||
new Ajax.Updater({success:'context-menu'}, '../../issues/context_menu/' + id, {asynchronous:true, evalScripts:true, onComplete:function(request){
|
||||
Effect.Appear('context-menu', {duration: 0.20});
|
||||
if (window.parseStylesheets) { window.parseStylesheets(); }
|
||||
}})
|
||||
function toggleIssuesSelection(el) {
|
||||
var boxes = el.getElementsBySelector('input[type=checkbox]');
|
||||
var all_checked = true;
|
||||
for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
|
||||
for (i = 0; i < boxes.length; i++) {
|
||||
if (all_checked) {
|
||||
boxes[i].checked = false;
|
||||
boxes[i].up('tr').removeClassName('context-menu-selection');
|
||||
} else if (boxes[i].checked == false) {
|
||||
boxes[i].checked = true;
|
||||
boxes[i].up('tr').addClassName('context-menu-selection');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,6 +197,28 @@ class IssuesControllerTest < Test::Unit::TestCase
|
|||
assert_not_nil assigns(:issue)
|
||||
assert_equal Issue.find(1), assigns(:issue)
|
||||
end
|
||||
|
||||
def test_get_edit_with_params
|
||||
@request.session[:user_id] = 2
|
||||
get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
|
||||
assert_response :success
|
||||
assert_template 'edit'
|
||||
|
||||
issue = assigns(:issue)
|
||||
assert_not_nil issue
|
||||
|
||||
assert_equal 5, issue.status_id
|
||||
assert_tag :select, :attributes => { :name => 'issue[status_id]' },
|
||||
:child => { :tag => 'option',
|
||||
:content => 'Closed',
|
||||
:attributes => { :selected => 'selected' } }
|
||||
|
||||
assert_equal 7, issue.priority_id
|
||||
assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
|
||||
:child => { :tag => 'option',
|
||||
:content => 'Urgent',
|
||||
:attributes => { :selected => 'selected' } }
|
||||
end
|
||||
|
||||
def test_post_edit
|
||||
@request.session[:user_id] = 2
|
||||
|
@ -305,12 +327,105 @@ class IssuesControllerTest < Test::Unit::TestCase
|
|||
# No email should be sent
|
||||
assert ActionMailer::Base.deliveries.empty?
|
||||
end
|
||||
|
||||
def test_context_menu
|
||||
|
||||
def test_bulk_edit
|
||||
@request.session[:user_id] = 2
|
||||
get :context_menu, :id => 1
|
||||
# update issues priority
|
||||
post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
|
||||
assert_response 302
|
||||
# check that the issues were updated
|
||||
assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
|
||||
assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
|
||||
end
|
||||
|
||||
def test_move_one_issue_to_another_project
|
||||
@request.session[:user_id] = 1
|
||||
post :move, :id => 1, :new_project_id => 2
|
||||
assert_redirected_to 'projects/ecookbook/issues'
|
||||
assert_equal 2, Issue.find(1).project_id
|
||||
end
|
||||
|
||||
def test_bulk_move_to_another_project
|
||||
@request.session[:user_id] = 1
|
||||
post :move, :ids => [1, 2], :new_project_id => 2
|
||||
assert_redirected_to 'projects/ecookbook/issues'
|
||||
# Issues moved to project 2
|
||||
assert_equal 2, Issue.find(1).project_id
|
||||
assert_equal 2, Issue.find(2).project_id
|
||||
# No tracker change
|
||||
assert_equal 1, Issue.find(1).tracker_id
|
||||
assert_equal 2, Issue.find(2).tracker_id
|
||||
end
|
||||
|
||||
def test_bulk_move_to_another_tracker
|
||||
@request.session[:user_id] = 1
|
||||
post :move, :ids => [1, 2], :new_tracker_id => 2
|
||||
assert_redirected_to 'projects/ecookbook/issues'
|
||||
assert_equal 2, Issue.find(1).tracker_id
|
||||
assert_equal 2, Issue.find(2).tracker_id
|
||||
end
|
||||
|
||||
def test_context_menu_one_issue
|
||||
@request.session[:user_id] = 2
|
||||
get :context_menu, :ids => [1]
|
||||
assert_response :success
|
||||
assert_template 'context_menu'
|
||||
assert_tag :tag => 'a', :content => 'Edit',
|
||||
:attributes => { :href => '/issues/edit/1',
|
||||
:class => 'icon-edit' }
|
||||
assert_tag :tag => 'a', :content => 'Closed',
|
||||
:attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
|
||||
:class => '' }
|
||||
assert_tag :tag => 'a', :content => 'Immediate',
|
||||
:attributes => { :href => '/issues/edit/1?issue%5Bpriority_id%5D=8',
|
||||
:class => '' }
|
||||
assert_tag :tag => 'a', :content => 'Dave Lopper',
|
||||
:attributes => { :href => '/issues/edit/1?issue%5Bassigned_to_id%5D=3',
|
||||
:class => '' }
|
||||
assert_tag :tag => 'a', :content => 'Copy',
|
||||
:attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
|
||||
:class => 'icon-copy' }
|
||||
assert_tag :tag => 'a', :content => 'Move',
|
||||
:attributes => { :href => '/issues/move?ids%5B%5D=1',
|
||||
:class => 'icon-move' }
|
||||
assert_tag :tag => 'a', :content => 'Delete',
|
||||
:attributes => { :href => '/issues/destroy?ids%5B%5D=1',
|
||||
:class => 'icon-del' }
|
||||
end
|
||||
|
||||
def test_context_menu_one_issue_by_anonymous
|
||||
get :context_menu, :ids => [1]
|
||||
assert_response :success
|
||||
assert_template 'context_menu'
|
||||
assert_tag :tag => 'a', :content => 'Delete',
|
||||
:attributes => { :href => '#',
|
||||
:class => 'icon-del disabled' }
|
||||
end
|
||||
|
||||
def test_context_menu_multiple_issues_of_same_project
|
||||
@request.session[:user_id] = 2
|
||||
get :context_menu, :ids => [1, 2]
|
||||
assert_response :success
|
||||
assert_template 'context_menu'
|
||||
assert_tag :tag => 'a', :content => 'Edit',
|
||||
:attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2',
|
||||
:class => 'icon-edit' }
|
||||
assert_tag :tag => 'a', :content => 'Move',
|
||||
:attributes => { :href => '/issues/move?ids%5B%5D=1&ids%5B%5D=2',
|
||||
:class => 'icon-move' }
|
||||
assert_tag :tag => 'a', :content => 'Delete',
|
||||
:attributes => { :href => '/issues/destroy?ids%5B%5D=1&ids%5B%5D=2',
|
||||
:class => 'icon-del' }
|
||||
end
|
||||
|
||||
def test_context_menu_multiple_issues_of_different_project
|
||||
@request.session[:user_id] = 2
|
||||
get :context_menu, :ids => [1, 2, 4]
|
||||
assert_response :success
|
||||
assert_template 'context_menu'
|
||||
assert_tag :tag => 'a', :content => 'Delete',
|
||||
:attributes => { :href => '#',
|
||||
:class => 'icon-del disabled' }
|
||||
end
|
||||
|
||||
def test_destroy
|
||||
|
|
|
@ -93,32 +93,6 @@ class ProjectsControllerTest < Test::Unit::TestCase
|
|||
assert_nil Project.find_by_id(1)
|
||||
end
|
||||
|
||||
def test_bulk_edit_issues
|
||||
@request.session[:user_id] = 2
|
||||
# update issues priority
|
||||
post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
|
||||
assert_response 302
|
||||
# check that the issues were updated
|
||||
assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
|
||||
assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
|
||||
end
|
||||
|
||||
def test_move_issues_to_another_project
|
||||
@request.session[:user_id] = 1
|
||||
post :move_issues, :id => 1, :issue_ids => [1, 2], :new_project_id => 2
|
||||
assert_redirected_to 'projects/ecookbook/issues'
|
||||
assert_equal 2, Issue.find(1).project_id
|
||||
assert_equal 2, Issue.find(2).project_id
|
||||
end
|
||||
|
||||
def test_move_issues_to_another_tracker
|
||||
@request.session[:user_id] = 1
|
||||
post :move_issues, :id => 1, :issue_ids => [1, 2], :new_tracker_id => 2
|
||||
assert_redirected_to 'projects/ecookbook/issues'
|
||||
assert_equal 2, Issue.find(1).tracker_id
|
||||
assert_equal 2, Issue.find(2).tracker_id
|
||||
end
|
||||
|
||||
def test_list_files
|
||||
get :list_files, :id => 1
|
||||
assert_response :success
|
||||
|
|
Loading…
Reference in New Issue