Merged IssuesController change_status and add_note actions.

The 'Change status' specific form removed and now accessible from issue/show view with no additional request (click on 'Update' to show the form).
The 'Change issue status' permission is removed. To change the status, the user just needs to have either 'Edit' or 'Add note' permissions and some workflow transitions allowed.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1043 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2008-01-06 17:06:14 +00:00
parent 4a729036bf
commit 976a31e941
39 changed files with 326 additions and 133 deletions

View File

@ -21,7 +21,7 @@ class IssuesController < ApplicationController
before_filter :find_optional_project, :only => [:index, :changes] before_filter :find_optional_project, :only => [:index, :changes]
accept_key_auth :index, :changes accept_key_auth :index, :changes
cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ] cache_sweeper :issue_sweeper, :only => [ :edit, :update, :destroy ]
helper :projects helper :projects
include ProjectsHelper include ProjectsHelper
@ -82,7 +82,8 @@ class IssuesController < ApplicationController
def show def show
@custom_values = @issue.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position") @custom_values = @issue.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
@status_options = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker) @status_options = @issue.new_statuses_allowed_to(User.current)
@activities = Enumeration::get_values('ACTI')
respond_to do |format| respond_to do |format|
format.html { render :template => 'issues/show.rhtml' } format.html { render :template => 'issues/show.rhtml' }
format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
@ -115,47 +116,42 @@ class IssuesController < ApplicationController
end end
end end
def add_note # Attributes that can be updated on workflow transition
# TODO: make it configurable (at least per role)
UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
def update
@status_options = @issue.new_statuses_allowed_to(User.current)
@activities = Enumeration::get_values('ACTI')
journal = @issue.init_journal(User.current, params[:notes]) journal = @issue.init_journal(User.current, params[:notes])
attachments = attach_files(@issue, params[:attachments]) # User can change issue attributes only if a workflow transition is allowed
attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} if !@status_options.empty? && params[:issue]
if journal.save attrs = params[:issue].dup
flash[:notice] = l(:notice_successful_update) attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) }
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') attrs.delete(:status_id) unless @status_options.detect {|s| s.id.to_s == attrs[:status_id].to_s}
redirect_to :action => 'show', :id => @issue @issue.attributes = attrs
return
end end
show if request.post?
end attachments = attach_files(@issue, params[:attachments])
attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
def change_status if @issue.save
@status_options = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker) # Log spend time
@new_status = IssueStatus.find(params[:new_status_id]) if current_role.allowed_to?(:log_time)
if params[:confirm] @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
begin @time_entry.attributes = params[:time_entry]
journal = @issue.init_journal(User.current, params[:notes]) @time_entry.save
@issue.status = @new_status end
if @issue.update_attributes(params[:issue]) if !journal.new_record?
attachments = attach_files(@issue, params[:attachments]) # Only send notification if something was actually changed
attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
# Log time
if current_role.allowed_to?(:log_time)
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
@time_entry.attributes = params[:time_entry]
@time_entry.save
end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
redirect_to :action => 'show', :id => @issue
end end
rescue ActiveRecord::StaleObjectError redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
# Optimistic locking exception
flash[:error] = l(:notice_locking_conflict)
end end
end end
@assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user } rescue ActiveRecord::StaleObjectError
@activities = Enumeration::get_values('ACTI') # Optimistic locking exception
flash.now[:error] = l(:notice_locking_conflict)
end end
def destroy def destroy
@ -177,11 +173,11 @@ class IssuesController < ApplicationController
def context_menu def context_menu
@priorities = Enumeration.get_values('IPRI').reverse @priorities = Enumeration.get_values('IPRI').reverse
@statuses = IssueStatus.find(:all, :order => 'position') @statuses = IssueStatus.find(:all, :order => 'position')
@allowed_statuses = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker) @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@assignables = @issue.assignable_users @assignables = @issue.assignable_users
@assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to) @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
@can = {:edit => User.current.allowed_to?(:edit_issues, @project), @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
:change_status => User.current.allowed_to?(:change_issue_status, @project), :assign => (@allowed_statuses.any? || User.current.allowed_to?(:edit_issues, @project)),
:add => User.current.allowed_to?(:add_issues, @project), :add => User.current.allowed_to?(:add_issues, @project),
:move => User.current.allowed_to?(:move_issues, @project), :move => User.current.allowed_to?(:move_issues, @project),
:copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), :copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),

View File

@ -252,11 +252,9 @@ class ProjectsController < ApplicationController
redirect_to :controller => 'issues', :action => 'index', :project_id => @project redirect_to :controller => 'issues', :action => 'index', :project_id => @project
return return
end end
if current_role && User.current.allowed_to?(:change_issue_status, @project) # Find potential statuses the user could be allowed to switch issues to
# Find potential statuses the user could be allowed to switch issues to @available_statuses = Workflow.find(:all, :include => :new_status,
@available_statuses = Workflow.find(:all, :include => :new_status, :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
:conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
end
render :update do |page| render :update do |page|
page.hide 'query_form' page.hide 'query_form'
page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form' page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'

View File

@ -180,6 +180,13 @@ class Issue < ActiveRecord::Base
project.assignable_users project.assignable_users
end end
# Returns an array of status that user is able to apply
def new_statuses_allowed_to(user)
statuses = status.find_new_statuses_allowed_to(user.role_for_project(project), tracker)
statuses << status unless statuses.empty?
statuses.uniq.sort
end
# Returns the mail adresses of users that should be notified for the issue # Returns the mail adresses of users that should be notified for the issue
def recipients def recipients
recipients = project.recipients recipients = project.recipients

View File

@ -56,6 +56,10 @@ class IssueStatus < ActiveRecord::Base
false false
end end
def <=>(status)
position <=> status.position
end
def to_s; name end def to_s; name end
private private

View File

@ -2,7 +2,7 @@
<fieldset class="box"><legend><%= l(:label_bulk_edit_selected_issues) %></legend> <fieldset class="box"><legend><%= l(:label_bulk_edit_selected_issues) %></legend>
<p> <p>
<% if @available_statuses %> <% if @available_statuses.any? %>
<label><%= l(:field_status) %>: <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> <%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %></label>
<% end %> <% end %>

View File

@ -0,0 +1,42 @@
<% labelled_tabular_form_for(:issue, @issue, :url => {:action => 'update', :id => @issue}, :html => {:multipart => true}) do |f| %>
<div class="box">
<% unless @status_options.empty? %>
<%= f.hidden_field :lock_version %>
<fieldset><legend><%= l(:label_change_properties) %></legend>
<div class="splitcontentleft">
<p><%= f.select :status_id, (@status_options.collect {|p| [p.name, p.id]}), :required => true %></p>
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
</div>
<div class="splitcontentright">
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
<p><%= f.select :fixed_version_id, (@project.versions.sort.collect {|v| [v.name, v.id]}), { :include_blank => true } %></p>
</div>
</fieldset>
<% end%>
<% if authorize_for('timelog', 'edit') %>
<fieldset><legend><%= l(:button_log_time) %></legend>
<% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
<div class="splitcontentleft">
<p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
</div>
<div class="splitcontentright">
<p><%= time_entry.text_field :comments, :size => 40 %></p>
<p><%= time_entry.select :activity_id, (@activities.collect {|p| [p.name, p.id]}) %></p>
</div>
<% end %>
</fieldset>
<% end %>
<fieldset><legend><%= l(:field_notes) %></legend>
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
<%= wikitoolbar_for 'notes' %>
<p id="attachments_p"><label><%=l(:label_attachment_new)%>
<%= image_to_function "add.png", "addFileField();return false" %></label>
<%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
</fieldset>
</div>
<%= submit_tag l(:button_submit) %>
<% end %>

View File

@ -1,38 +0,0 @@
<h2><%=l(:label_issue)%> #<%= @issue.id %>: <%=h @issue.subject %></h2>
<%= error_messages_for 'issue' %>
<% labelled_tabular_form_for(:issue, @issue, :url => {:action => 'change_status', :id => @issue}, :html => {:multipart => true}) do |f| %>
<%= hidden_field_tag 'confirm', 1 %>
<%= hidden_field_tag 'new_status_id', @new_status.id %>
<%= f.hidden_field :lock_version %>
<div class="box">
<div class="splitcontentleft">
<p><label><%=l(:label_issue_status_new)%></label> <%= @new_status.name %></p>
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
<p><%= f.select :fixed_version_id, (@project.versions.sort.collect {|v| [v.name, v.id]}), { :include_blank => true } %></p>
</div>
<div class="splitcontentright">
<% if authorize_for('timelog', 'edit') %>
<% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
<p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
<p><%= time_entry.text_field :comments, :size => 40 %></p>
<p><%= time_entry.select :activity_id, (@activities.collect {|p| [p.name, p.id]}) %></p>
<% end %>
<% end %>
</div>
<div class="clear"></div>
<p><label for="notes"><%= l(:field_notes) %></label>
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %></p>
<p id="attachments_p"><label><%=l(:label_attachment_new)%>
<%= image_to_function "add.png", "addFileField();return false" %></label>
<%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
</div>
<%= submit_tag l(:button_save) %>
<% end %>

View File

@ -6,8 +6,8 @@
<a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a> <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
<ul> <ul>
<% @statuses.each do |s| %> <% @statuses.each do |s| %>
<li><%= context_menu_link s.name, {:controller => 'issues', :action => 'change_status', :id => @issue, :new_status_id => s}, <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:status_id => s}},
:selected => (s == @issue.status), :disabled => !(@can[:change_status] && @allowed_statuses.include?(s)) %></li> :selected => (s == @issue.status), :disabled => !(@allowed_statuses.include?(s)) %></li>
<% end %> <% end %>
</ul> </ul>
</li> </li>
@ -24,11 +24,11 @@
<a href="#" class="submenu"><%= l(:field_assigned_to) %></a> <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
<ul> <ul>
<% @assignables.each do |u| %> <% @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, <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:assigned_to_id => u}, :back_to => back_to}, :method => :post,
:selected => (u == @issue.assigned_to), :disabled => !(@can[:edit] || @can[:change_status]) %></li> :selected => (u == @issue.assigned_to), :disabled => !@can[:assign] %></li>
<% end %> <% end %>
<li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => '', :back_to => back_to}, :method => :post, <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:assigned_to_id => nil}, :back_to => back_to}, :method => :post,
:selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li> :selected => @issue.assigned_to.nil?, :disabled => !@can[:assign] %></li>
</ul> </ul>
</li> </li>
<li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue}, <li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue},

View File

@ -1,5 +1,5 @@
<div class="contextual"> <div class="contextual">
<%= show_and_goto_link(l(:label_add_note), 'add-note', :class => 'icon icon-note') if authorize_for('issues', 'add_note') %> <%= show_and_goto_link(l(:button_update), 'update', :class => 'icon icon-note') if authorize_for('issues', 'update') %>
<%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %> <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %> <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
<%= watcher_tag(@issue, User.current) %> <%= watcher_tag(@issue, User.current) %>
@ -81,16 +81,6 @@ end %>
</div> </div>
<% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
<% form_tag({:controller => 'issues', :action => 'change_status', :id => @issue}) do %>
<p><%=l(:label_change_status)%> :
<select name="new_status_id">
<%= options_from_collection_for_select @status_options, "id", "name", @issue.status_id %>
</select>
<%= submit_tag l(:button_change) %></p>
<% end %>
<% end %>
<% if @journals.any? %> <% if @journals.any? %>
<div id="history"> <div id="history">
<h3><%=l(:label_history)%></h3> <h3><%=l(:label_history)%></h3>
@ -98,18 +88,12 @@ end %>
</div> </div>
<% end %> <% end %>
<% if authorize_for('issues', 'add_note') %> <% if authorize_for('issues', 'update') %>
<a name="add-note-anchor"></a> <a name="update-anchor"></a>
<div id="add-note" class="box" style="display:none;"> <div id="update" style="display:none;">
<h3><%= l(:label_add_note) %></h3> <h3><%= l(:button_update) %></h3>
<% form_tag({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular", :multipart => true) do %> <%= render :partial => 'update' %>
<p><label for="notes"><%=l(:field_notes)%></label> <%= toggle_link l(:button_cancel), 'update' %>
<%= text_area_tag 'notes', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %></p>
<%= wikitoolbar_for 'notes' %>
<%= render :partial => 'attachments/form' %>
<%= submit_tag l(:button_add) %>
<%= toggle_link l(:button_cancel), 'add-note' %>
<% end %>
</div> </div>
<% end %> <% end %>

View File

@ -0,0 +1,4 @@
<h2><%= @issue.tracker.name %> #<%= @issue.id %>: <%=h @issue.subject %></h2>
<%= error_messages_for 'issue' %>
<%= render :partial => 'update' %>

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -462,6 +462,7 @@ label_registration_manual_activation: manual account activation
label_registration_automatic_activation: automatic account activation label_registration_automatic_activation: automatic account activation
label_display_per_page: 'Per page: %s' label_display_per_page: 'Per page: %s'
label_age: Age label_age: Age
label_change_properties: Change properties
button_login: Login button_login: Login
button_submit: Submit button_submit: Submit
@ -498,6 +499,7 @@ button_rename: Rename
button_change_password: Change password button_change_password: Change password
button_copy: Copy button_copy: Copy
button_annotate: Annotate button_annotate: Annotate
button_update: Update
status_active: active status_active: active
status_registered: registered status_registered: registered

View File

@ -560,3 +560,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -462,6 +462,7 @@ label_registration_manual_activation: activation manuelle du compte
label_registration_automatic_activation: activation automatique du compte label_registration_automatic_activation: activation automatique du compte
label_display_per_page: 'Par page: %s' label_display_per_page: 'Par page: %s'
label_age: Age label_age: Age
label_change_properties: Changer les propriétés
button_login: Connexion button_login: Connexion
button_submit: Soumettre button_submit: Soumettre
@ -498,6 +499,7 @@ button_rename: Renommer
button_change_password: Changer de mot de passe button_change_password: Changer de mot de passe
button_copy: Copier button_copy: Copier
button_annotate: Annoter button_annotate: Annoter
button_update: Mettre à jour
status_active: actif status_active: actif
status_registered: enregistré status_registered: enregistré

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -558,3 +558,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -558,3 +558,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -558,3 +558,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -558,3 +558,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -560,3 +560,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
text_load_default_configuration: Load the default configuration text_load_default_configuration: Load the default configuration
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_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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s" error_can_t_load_default_data: "Default configuration could not be loaded: %s"
button_update: Update
label_change_properties: Change properties

View File

@ -32,10 +32,9 @@ Redmine::AccessControl.map do |map|
:reports => :issue_report}, :public => true :reports => :issue_report}, :public => true
map.permission :add_issues, {:projects => :add_issue} map.permission :add_issues, {:projects => :add_issue}
map.permission :edit_issues, {:projects => :bulk_edit_issues, map.permission :edit_issues, {:projects => :bulk_edit_issues,
:issues => [:edit, :destroy_attachment]} :issues => [:edit, :update, :destroy_attachment]}
map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]} map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
map.permission :add_issue_notes, {:issues => :add_note} map.permission :add_issue_notes, {:issues => :update}
map.permission :change_issue_status, {:issues => :change_status}, :require => :loggedin
map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
map.permission :delete_issues, {:issues => :destroy}, :require => :member map.permission :delete_issues, {:issues => :destroy}, :require => :member
# Queries # Queries

View File

@ -53,7 +53,6 @@ module Redmine
:edit_issues, :edit_issues,
:manage_issue_relations, :manage_issue_relations,
:add_issue_notes, :add_issue_notes,
:change_issue_status,
:save_queries, :save_queries,
:view_gantt, :view_gantt,
:view_calendar, :view_calendar,
@ -74,7 +73,6 @@ module Redmine
:position => 3, :position => 3,
:permissions => [:add_issues, :permissions => [:add_issues,
:add_issue_notes, :add_issue_notes,
:change_issue_status,
:save_queries, :save_queries,
:view_gantt, :view_gantt,
:view_calendar, :view_calendar,
@ -90,7 +88,6 @@ module Redmine
Role.non_member.update_attribute :permissions, [:add_issues, Role.non_member.update_attribute :permissions, [:add_issues,
:add_issue_notes, :add_issue_notes,
:change_issue_status,
:save_queries, :save_queries,
:view_gantt, :view_gantt,
:view_calendar, :view_calendar,

View File

@ -31,3 +31,12 @@ enumerations_008:
name: Immediate name: Immediate
id: 8 id: 8
opt: IPRI opt: IPRI
enumerations_009:
name: Design
id: 9
opt: ACTI
enumerations_010:
name: Development
id: 10
opt: ACTI

View File

@ -9,7 +9,6 @@ roles_004:
- :edit_issues - :edit_issues
- :manage_issue_relations - :manage_issue_relations
- :add_issue_notes - :add_issue_notes
- :change_issue_status
- :move_issues - :move_issues
- :save_queries - :save_queries
- :view_gantt - :view_gantt
@ -34,6 +33,7 @@ roles_005:
builtin: 2 builtin: 2
permissions: | permissions: |
--- ---
- :add_issue_notes
- :view_gantt - :view_gantt
- :view_calendar - :view_calendar
- :view_time_entries - :view_time_entries
@ -58,7 +58,6 @@ roles_001:
- :edit_issues - :edit_issues
- :manage_issue_relations - :manage_issue_relations
- :add_issue_notes - :add_issue_notes
- :change_issue_status
- :move_issues - :move_issues
- :delete_issues - :delete_issues
- :manage_public_queries - :manage_public_queries
@ -99,7 +98,6 @@ roles_002:
- :edit_issues - :edit_issues
- :manage_issue_relations - :manage_issue_relations
- :add_issue_notes - :add_issue_notes
- :change_issue_status
- :move_issues - :move_issues
- :delete_issues - :delete_issues
- :manage_public_queries - :manage_public_queries
@ -137,7 +135,6 @@ roles_003:
- :edit_issues - :edit_issues
- :manage_issue_relations - :manage_issue_relations
- :add_issue_notes - :add_issue_notes
- :change_issue_status
- :move_issues - :move_issues
- :delete_issues - :delete_issues
- :manage_public_queries - :manage_public_queries

View File

@ -14,6 +14,7 @@ users_004:
auth_source_id: auth_source_id:
mail_notification: true mail_notification: true
login: rhill login: rhill
type: User
users_001: users_001:
created_on: 2006-07-19 19:12:21 +02:00 created_on: 2006-07-19 19:12:21 +02:00
status: 1 status: 1
@ -29,6 +30,7 @@ users_001:
auth_source_id: auth_source_id:
mail_notification: true mail_notification: true
login: admin login: admin
type: User
users_002: users_002:
created_on: 2006-07-19 19:32:09 +02:00 created_on: 2006-07-19 19:32:09 +02:00
status: 1 status: 1
@ -44,6 +46,7 @@ users_002:
auth_source_id: auth_source_id:
mail_notification: true mail_notification: true
login: jsmith login: jsmith
type: User
users_003: users_003:
created_on: 2006-07-19 19:33:19 +02:00 created_on: 2006-07-19 19:33:19 +02:00
status: 1 status: 1
@ -59,6 +62,7 @@ users_003:
auth_source_id: auth_source_id:
mail_notification: true mail_notification: true
login: dlopper login: dlopper
type: User
users_005: users_005:
id: 5 id: 5
created_on: 2006-07-19 19:33:19 +02:00 created_on: 2006-07-19 19:33:19 +02:00
@ -75,3 +79,22 @@ users_005:
auth_source_id: auth_source_id:
mail_notification: true mail_notification: true
login: dlopper2 login: dlopper2
type: User
users_006:
id: 6
created_on: 2006-07-19 19:33:19 +02:00
status: 1
last_login_on:
language: ''
hashed_password: 1
updated_on: 2006-07-19 19:33:19 +02:00
admin: false
mail: ''
lastname: Anonymous
firstname: ''
auth_source_id:
mail_notification: false
login: ''
type: AnonymousUser

View File

@ -32,7 +32,8 @@ class IssuesControllerTest < Test::Unit::TestCase
:issue_categories, :issue_categories,
:enabled_modules, :enabled_modules,
:enumerations, :enumerations,
:attachments :attachments,
:workflows
def setup def setup
@controller = IssuesController.new @controller = IssuesController.new
@ -94,11 +95,35 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_equal 'application/atom+xml', @response.content_type assert_equal 'application/atom+xml', @response.content_type
end end
def test_show def test_show_by_anonymous
get :show, :id => 1 get :show, :id => 1
assert_response :success assert_response :success
assert_template 'show.rhtml' assert_template 'show.rhtml'
assert_not_nil assigns(:issue) assert_not_nil assigns(:issue)
assert_equal Issue.find(1), assigns(:issue)
# anonymous role is allowed to add a note
assert_tag :tag => 'form',
:descendant => { :tag => 'fieldset',
:child => { :tag => 'legend',
:content => /Notes/ } }
end
def test_show_by_manager
@request.session[:user_id] = 2
get :show, :id => 1
assert_response :success
assert_tag :tag => 'form',
:descendant => { :tag => 'fieldset',
:child => { :tag => 'legend',
:content => /Change properties/ } },
:descendant => { :tag => 'fieldset',
:child => { :tag => 'legend',
:content => /Log time/ } },
:descendant => { :tag => 'fieldset',
:child => { :tag => 'legend',
:content => /Notes/ } }
end end
def test_get_edit def test_get_edit
@ -129,21 +154,100 @@ class IssuesControllerTest < Test::Unit::TestCase
assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}") assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
end end
def test_post_change_status def test_get_update
@request.session[:user_id] = 2
get :update, :id => 1
assert_response :success
assert_template 'update'
end
def test_update_with_status_and_assignee_change
issue = Issue.find(1) issue = Issue.find(1)
assert_equal 1, issue.status_id assert_equal 1, issue.status_id
@request.session[:user_id] = 2 @request.session[:user_id] = 2
post :change_status, :id => 1, post :update,
:new_status_id => 2, :id => 1,
:issue => { :assigned_to_id => 3 }, :issue => { :status_id => 2, :assigned_to_id => 3 },
:notes => 'Assigned to dlopper', :notes => 'Assigned to dlopper'
:confirm => 1
assert_redirected_to 'issues/show/1' assert_redirected_to 'issues/show/1'
issue.reload issue.reload
assert_equal 2, issue.status_id assert_equal 2, issue.status_id
j = issue.journals.find(:first, :order => 'created_on DESC') j = issue.journals.find(:first, :order => 'id DESC')
assert_equal 'Assigned to dlopper', j.notes assert_equal 'Assigned to dlopper', j.notes
assert_equal 2, j.details.size assert_equal 2, j.details.size
mail = ActionMailer::Base.deliveries.last
assert mail.body.include?("Status changed from New to Assigned")
end
def test_update_with_note_only
notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
# anonymous user
post :update,
:id => 1,
:notes => notes
assert_redirected_to 'issues/show/1'
j = Issue.find(1).journals.find(:first, :order => 'id DESC')
assert_equal notes, j.notes
assert_equal 0, j.details.size
assert_equal User.anonymous, j.user
mail = ActionMailer::Base.deliveries.last
assert mail.body.include?(notes)
end
def test_update_with_note_and_spent_time
@request.session[:user_id] = 2
spent_hours_before = Issue.find(1).spent_hours
post :update,
:id => 1,
:notes => '2.5 hours added',
:time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
assert_redirected_to 'issues/show/1'
issue = Issue.find(1)
j = issue.journals.find(:first, :order => 'id DESC')
assert_equal '2.5 hours added', j.notes
assert_equal 0, j.details.size
t = issue.time_entries.find(:first, :order => 'id DESC')
assert_not_nil t
assert_equal 2.5, t.hours
assert_equal spent_hours_before + 2.5, issue.spent_hours
end
def test_update_with_attachment_only
# anonymous user
post :update,
:id => 1,
:notes => '',
:attachments => [ test_uploaded_file('testfile.txt', 'text/plain') ]
assert_redirected_to 'issues/show/1'
j = Issue.find(1).journals.find(:first, :order => 'id DESC')
assert j.notes.blank?
assert_equal 1, j.details.size
assert_equal 'testfile.txt', j.details.first.value
assert_equal User.anonymous, j.user
mail = ActionMailer::Base.deliveries.last
assert mail.body.include?('testfile.txt')
end
def test_update_with_no_change
issue = Issue.find(1)
issue.journals.clear
ActionMailer::Base.deliveries.clear
post :update,
:id => 1,
:notes => ''
assert_redirected_to 'issues/show/1'
issue.reload
assert issue.journals.empty?
# No email should be sent
assert ActionMailer::Base.deliveries.empty?
end end
def test_context_menu def test_context_menu

View File

@ -38,7 +38,9 @@ class IssuesTest < ActionController::IntegrationTest
def test_issue_attachements def test_issue_attachements
log_user('jsmith', 'jsmith') log_user('jsmith', 'jsmith')
post "issues/add_note/1", { :notes => 'Some notes', 'attachments[]' => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/testfile.txt', 'text/plain') } post 'issues/update/1',
:notes => 'Some notes',
:attachments => ([] << ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/testfile.txt', 'text/plain'))
assert_redirected_to "issues/show/1" assert_redirected_to "issues/show/1"
# make sure attachment was saved # make sure attachment was saved

View File

@ -53,6 +53,10 @@ class Test::Unit::TestCase
assert_redirected_to "my/page" assert_redirected_to "my/page"
assert_equal login, User.find(session[:user_id]).login assert_equal login, User.find(session[:user_id]).login
end end
def test_uploaded_file(name, mime)
ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + "/files/#{name}", mime)
end
end end

View File

@ -155,6 +155,27 @@ module ActiveRecord #:nodoc:
# end # end
# end # end
class Errors
include GLoc
def full_messages
full_messages = []
@errors.each_key do |attr|
@errors[attr].each do |msg|
next if msg.nil?
if attr == "base"
full_messages << (msg.is_a?(Symbol) ? l(msg) : msg)
else
full_messages << @base.class.human_attribute_name(attr) + " " + (msg.is_a?(Symbol) ? l(msg) : msg)
end
end
end
full_messages
end
end
module Validations #:nodoc: module Validations #:nodoc:
module ClassMethods module ClassMethods
# The default Rails version of this function creates an error message and then # The default Rails version of this function creates an error message and then