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
@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]) }
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
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
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
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 ]
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])
+++ /dev/null
-<div id="bulk-edit-fields">
-<fieldset class="box"><legend><%= l(:label_bulk_edit_selected_issues) %></legend>
-
-<p>
-<% if @available_statuses.any? %>
-<label><%= l(:field_status) %>:
-<%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %></label>
-<% end %>
-<label><%= l(:field_priority) %>:
-<%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(Enumeration.get_values('IPRI'), :id, :name)) %></label>
-<label><%= l(:field_category) %>:
-<%= select_tag('category_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.issue_categories, :id, :name)) %></label>
-</p>
-<p>
-<label><%= l(:field_assigned_to) %>:
-<%= select_tag('assigned_to_id', content_tag('option', l(:label_no_change_option), :value => '') +
- content_tag('option', l(:label_nobody), :value => 'none') +
- options_from_collection_for_select(@project.assignable_users, :id, :name)) %></label>
-<label><%= l(:field_fixed_version) %>:
-<%= select_tag('fixed_version_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.versions, :id, :name)) %></label>
-</p>
-
-<p>
-<label><%= l(:field_start_date) %>:
-<%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %></label>
-<label><%= l(:field_due_date) %>:
-<%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %></label>
-<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>
-</div>
-<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| %>
<% 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')}')" %>
-<% 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>
<% 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 />
<% end %>
</tbody>
</table>
+<% end %>
<% else %>
- <i><%=l(:label_no_data)%></i>
-<% end %>
\ No newline at end of file
+ <p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
--- /dev/null
+<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) %>:
+<%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %></label>
+<% end %>
+<label><%= l(:field_priority) %>:
+<%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(Enumeration.get_values('IPRI'), :id, :name)) %></label>
+<label><%= l(:field_category) %>:
+<%= select_tag('category_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.issue_categories, :id, :name)) %></label>
+</p>
+<p>
+<label><%= l(:field_assigned_to) %>:
+<%= select_tag('assigned_to_id', content_tag('option', l(:label_no_change_option), :value => '') +
+ content_tag('option', l(:label_nobody), :value => 'none') +
+ options_from_collection_for_select(@project.assignable_users, :id, :name)) %></label>
+<label><%= l(:field_fixed_version) %>:
+<%= select_tag('fixed_version_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.versions, :id, :name)) %></label>
+</p>
+
+<p>
+<label><%= l(:field_start_date) %>:
+<%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %></label>
+<label><%= l(:field_due_date) %>:
+<%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %></label>
+<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>
+</fieldset>
+
+<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 %>
-<% 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>
<%= 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 %>
<% 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) %>
<p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p>
<% end %>
<% end %>
-<% end %>
<% content_for :sidebar do %>
<%= render :partial => 'issues/sidebar' %>
<% 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({})' %>
--- /dev/null
+<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({}, :id => 'move_form') do %>
+<%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
+
+<div class="box tabular">
+<p><label for="new_project_id"><%=l(:field_project)%> :</label>
+<%= select_tag "new_project_id",
+ 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>
+
+<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 %>
<%= 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>
<% 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)) -%>
+++ /dev/null
-<h2><%=l(:button_move)%></h2>
-
-
-<% form_tag({:action => 'move_issues', :id => @project}, :class => 'tabular', :id => 'move_form') do %>
-
-<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]-->
-<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},
- :method => :get,
- :update => 'content',
- :with => "Form.serialize('move_form')") %></p>
-
-<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 %>
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) ?'
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) ?'
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) ?'
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
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) ?'
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) ?'
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
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) ?'
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) ?'
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) ?'
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) ?'
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) ?'
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) ?'
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) ?'
setting_user_format: Users display format\r
text_status_changed_by_changeset: Applied in changeset %s.\r
label_more: More\r
+text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'\r
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) ?'
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) ?'
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) ?'
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) ?'
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) ?'
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) ?'
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) ?'
: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
+/* 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++) {
}
}
-/* 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++) {
+/* 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');
+ }
}
}
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
# No email should be sent
assert ActionMailer::Base.deliveries.empty?
end
+
+ def test_bulk_edit
+ @request.session[:user_id] = 2
+ # 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
+ 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, :id => 1
+ 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
assert_nil Project.find_by_id(1)
end
\r
- 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\r
-
- 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
- \r
def test_list_files\r
get :list_files, :id => 1\r
assert_response :success\r