diff options
39 files changed, 326 insertions, 133 deletions
diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 39a1e2d13..11112289e 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -21,7 +21,7 @@ class IssuesController < ApplicationController before_filter :find_optional_project, :only => [: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 include ProjectsHelper @@ -82,7 +82,8 @@ class IssuesController < ApplicationController def show @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") - @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| 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") } @@ -115,47 +116,42 @@ class IssuesController < ApplicationController 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]) - attachments = attach_files(@issue, params[:attachments]) - attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} - if journal.save - flash[:notice] = l(:notice_successful_update) - Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') - redirect_to :action => 'show', :id => @issue - return + # User can change issue attributes only if a workflow transition is allowed + if !@status_options.empty? && params[:issue] + attrs = params[:issue].dup + attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } + attrs.delete(:status_id) unless @status_options.detect {|s| s.id.to_s == attrs[:status_id].to_s} + @issue.attributes = attrs end - show - end - - def change_status - @status_options = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker) - @new_status = IssueStatus.find(params[:new_status_id]) - if params[:confirm] - begin - journal = @issue.init_journal(User.current, params[:notes]) - @issue.status = @new_status - if @issue.update_attributes(params[:issue]) - attachments = attach_files(@issue, params[:attachments]) - 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 - + if request.post? + attachments = attach_files(@issue, params[:attachments]) + attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} + if @issue.save + # Log spend 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 + if !journal.new_record? + # Only send notification if something was actually changed flash[:notice] = l(:notice_successful_update) Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') - redirect_to :action => 'show', :id => @issue end - rescue ActiveRecord::StaleObjectError - # Optimistic locking exception - flash[:error] = l(:notice_locking_conflict) + redirect_to(params[:back_to] || {:action => 'show', :id => @issue}) end - end - @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user } - @activities = Enumeration::get_values('ACTI') + end + rescue ActiveRecord::StaleObjectError + # Optimistic locking exception + flash.now[:error] = l(:notice_locking_conflict) end def destroy @@ -177,11 +173,11 @@ class IssuesController < ApplicationController def context_menu @priorities = Enumeration.get_values('IPRI').reverse @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.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to) @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), :move => User.current.allowed_to?(:move_issues, @project), :copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a58eef719..2ae742dfa 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -252,11 +252,9 @@ class ProjectsController < ApplicationController redirect_to :controller => 'issues', :action => 'index', :project_id => @project return end - if current_role && User.current.allowed_to?(:change_issue_status, @project) - # 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 + # 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' diff --git a/app/models/issue.rb b/app/models/issue.rb index f7b01ea6a..419c6cdc7 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -180,6 +180,13 @@ class Issue < ActiveRecord::Base project.assignable_users 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 def recipients recipients = project.recipients diff --git a/app/models/issue_status.rb b/app/models/issue_status.rb index a5d228405..ddff9c005 100644 --- a/app/models/issue_status.rb +++ b/app/models/issue_status.rb @@ -56,6 +56,10 @@ class IssueStatus < ActiveRecord::Base false end + def <=>(status) + position <=> status.position + end + def to_s; name end private diff --git a/app/views/issues/_bulk_edit_form.rhtml b/app/views/issues/_bulk_edit_form.rhtml index bc3f62e6d..e9e1cef86 100644 --- a/app/views/issues/_bulk_edit_form.rhtml +++ b/app/views/issues/_bulk_edit_form.rhtml @@ -2,7 +2,7 @@ <fieldset class="box"><legend><%= l(:label_bulk_edit_selected_issues) %></legend> <p> -<% if @available_statuses %> +<% 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 %> diff --git a/app/views/issues/_update.rhtml b/app/views/issues/_update.rhtml new file mode 100644 index 000000000..3cf593806 --- /dev/null +++ b/app/views/issues/_update.rhtml @@ -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 %> diff --git a/app/views/issues/change_status.rhtml b/app/views/issues/change_status.rhtml deleted file mode 100644 index a1e294556..000000000 --- a/app/views/issues/change_status.rhtml +++ /dev/null @@ -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 %> diff --git a/app/views/issues/context_menu.rhtml b/app/views/issues/context_menu.rhtml index 3af49fb04..0f11bb943 100644 --- a/app/views/issues/context_menu.rhtml +++ b/app/views/issues/context_menu.rhtml @@ -6,8 +6,8 @@ <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a> <ul> <% @statuses.each do |s| %> - <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'change_status', :id => @issue, :new_status_id => s}, - :selected => (s == @issue.status), :disabled => !(@can[:change_status] && @allowed_statuses.include?(s)) %></li> + <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:status_id => s}}, + :selected => (s == @issue.status), :disabled => !(@allowed_statuses.include?(s)) %></li> <% end %> </ul> </li> @@ -24,11 +24,11 @@ <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[:edit] || @can[:change_status]) %></li> + <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[:assign] %></li> <% end %> - <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => '', :back_to => back_to}, :method => :post, - :selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li> + <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[:assign] %></li> </ul> </li> <li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue}, diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index 91b638216..8cd44424c 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -1,5 +1,5 @@ <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_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %> <%= watcher_tag(@issue, User.current) %> @@ -81,16 +81,6 @@ end %> </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? %> <div id="history"> <h3><%=l(:label_history)%></h3> @@ -98,18 +88,12 @@ end %> </div> <% end %> -<% if authorize_for('issues', 'add_note') %> - <a name="add-note-anchor"></a> - <div id="add-note" class="box" style="display:none;"> - <h3><%= l(:label_add_note) %></h3> - <% form_tag({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular", :multipart => true) do %> - <p><label for="notes"><%=l(:field_notes)%></label> - <%= 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 %> +<% if authorize_for('issues', 'update') %> + <a name="update-anchor"></a> + <div id="update" style="display:none;"> + <h3><%= l(:button_update) %></h3> + <%= render :partial => 'update' %> + <%= toggle_link l(:button_cancel), 'update' %> </div> <% end %> diff --git a/app/views/issues/update.rhtml b/app/views/issues/update.rhtml new file mode 100644 index 000000000..44e72da87 --- /dev/null +++ b/app/views/issues/update.rhtml @@ -0,0 +1,4 @@ +<h2><%= @issue.tracker.name %> #<%= @issue.id %>: <%=h @issue.subject %></h2> + +<%= error_messages_for 'issue' %> +<%= render :partial => 'update' %> diff --git a/lang/bg.yml b/lang/bg.yml index eeb6ef80b..57e7fd22d 100644 --- a/lang/bg.yml +++ b/lang/bg.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/cs.yml b/lang/cs.yml index e99fe061b..5cbf1133d 100644 --- a/lang/cs.yml +++ b/lang/cs.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/de.yml b/lang/de.yml index cd4e222ae..ba3dcda5b 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/en.yml b/lang/en.yml index 7a1a2622f..eae61d7bf 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -462,6 +462,7 @@ label_registration_manual_activation: manual account activation label_registration_automatic_activation: automatic account activation label_display_per_page: 'Per page: %s' label_age: Age +label_change_properties: Change properties button_login: Login button_submit: Submit @@ -498,6 +499,7 @@ button_rename: Rename button_change_password: Change password button_copy: Copy button_annotate: Annotate +button_update: Update status_active: active status_registered: registered diff --git a/lang/es.yml b/lang/es.yml index bae295586..1a3af0f5c 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -560,3 +560,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/fr.yml b/lang/fr.yml index d47feceb2..ff2bf792f 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -462,6 +462,7 @@ label_registration_manual_activation: activation manuelle du compte label_registration_automatic_activation: activation automatique du compte label_display_per_page: 'Par page: %s' label_age: Age +label_change_properties: Changer les propriétés button_login: Connexion button_submit: Soumettre @@ -498,6 +499,7 @@ button_rename: Renommer button_change_password: Changer de mot de passe button_copy: Copier button_annotate: Annoter +button_update: Mettre à jour status_active: actif status_registered: enregistré diff --git a/lang/he.yml b/lang/he.yml index e2277dc3d..d110ee8bf 100644 --- a/lang/he.yml +++ b/lang/he.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/it.yml b/lang/it.yml index 709f41774..bb9440ffd 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/ja.yml b/lang/ja.yml index 286fb5971..e4c13b34c 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -558,3 +558,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/ko.yml b/lang/ko.yml index 52c9b5431..790598b8d 100644 --- a/lang/ko.yml +++ b/lang/ko.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/nl.yml b/lang/nl.yml index 43e1704a4..2e18a6b7c 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -558,3 +558,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/pl.yml b/lang/pl.yml index 77ae6bb66..0ee0e3792 100644 --- a/lang/pl.yml +++ b/lang/pl.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/pt-br.yml b/lang/pt-br.yml index 55e8b3fae..21d21b793 100644 --- a/lang/pt-br.yml +++ b/lang/pt-br.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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."
error_can_t_load_default_data: "Default configuration could not be loaded: %s"
+button_update: Update
+label_change_properties: Change properties
diff --git a/lang/pt.yml b/lang/pt.yml index 0a5661997..c7f05f8c8 100644 --- a/lang/pt.yml +++ b/lang/pt.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/ro.yml b/lang/ro.yml index 1381adba9..6ebc3b0e5 100644 --- a/lang/ro.yml +++ b/lang/ro.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/ru.yml b/lang/ru.yml index b293dc309..569e96884 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/sr.yml b/lang/sr.yml index c842ec438..1d4f52539 100644 --- a/lang/sr.yml +++ b/lang/sr.yml @@ -558,3 +558,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/sv.yml b/lang/sv.yml index 85fb19bcb..75f297c20 100644 --- a/lang/sv.yml +++ b/lang/sv.yml @@ -558,3 +558,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/zh-tw.yml b/lang/zh-tw.yml index 719a2e868..5bf47d943 100644 --- a/lang/zh-tw.yml +++ b/lang/zh-tw.yml @@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lang/zh.yml b/lang/zh.yml index 05134751f..16f4c7195 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -560,3 +560,5 @@ notice_default_data_loaded: Default configuration successfully loaded. 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." error_can_t_load_default_data: "Default configuration could not be loaded: %s" +button_update: Update +label_change_properties: Change properties diff --git a/lib/redmine.rb b/lib/redmine.rb index 9b29257bd..2dca9ed50 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -32,10 +32,9 @@ Redmine::AccessControl.map do |map| :reports => :issue_report}, :public => true map.permission :add_issues, {:projects => :add_issue} 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 :add_issue_notes, {:issues => :add_note} - map.permission :change_issue_status, {:issues => :change_status}, :require => :loggedin + map.permission :add_issue_notes, {:issues => :update} map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin map.permission :delete_issues, {:issues => :destroy}, :require => :member # Queries diff --git a/lib/redmine/default_data/loader.rb b/lib/redmine/default_data/loader.rb index 0abb5572a..11bd2a0b4 100644 --- a/lib/redmine/default_data/loader.rb +++ b/lib/redmine/default_data/loader.rb @@ -53,7 +53,6 @@ module Redmine :edit_issues, :manage_issue_relations, :add_issue_notes, - :change_issue_status, :save_queries, :view_gantt, :view_calendar, @@ -74,7 +73,6 @@ module Redmine :position => 3, :permissions => [:add_issues, :add_issue_notes, - :change_issue_status, :save_queries, :view_gantt, :view_calendar, @@ -90,7 +88,6 @@ module Redmine Role.non_member.update_attribute :permissions, [:add_issues, :add_issue_notes, - :change_issue_status, :save_queries, :view_gantt, :view_calendar, diff --git a/test/fixtures/enumerations.yml b/test/fixtures/enumerations.yml index eeef99b5b..c90a997ee 100644 --- a/test/fixtures/enumerations.yml +++ b/test/fixtures/enumerations.yml @@ -31,3 +31,12 @@ enumerations_008: name: Immediate
id: 8
opt: IPRI
+enumerations_009:
+ name: Design
+ id: 9
+ opt: ACTI
+enumerations_010:
+ name: Development
+ id: 10
+ opt: ACTI
+
\ No newline at end of file diff --git a/test/fixtures/roles.yml b/test/fixtures/roles.yml index a089a98f9..c4d417a09 100644 --- a/test/fixtures/roles.yml +++ b/test/fixtures/roles.yml @@ -9,7 +9,6 @@ roles_004: - :edit_issues
- :manage_issue_relations
- :add_issue_notes
- - :change_issue_status
- :move_issues
- :save_queries
- :view_gantt
@@ -34,6 +33,7 @@ roles_005: builtin: 2
permissions: |
---
+ - :add_issue_notes
- :view_gantt
- :view_calendar
- :view_time_entries
@@ -58,7 +58,6 @@ roles_001: - :edit_issues
- :manage_issue_relations
- :add_issue_notes
- - :change_issue_status
- :move_issues
- :delete_issues
- :manage_public_queries
@@ -99,7 +98,6 @@ roles_002: - :edit_issues
- :manage_issue_relations
- :add_issue_notes
- - :change_issue_status
- :move_issues
- :delete_issues
- :manage_public_queries
@@ -137,7 +135,6 @@ roles_003: - :edit_issues
- :manage_issue_relations
- :add_issue_notes
- - :change_issue_status
- :move_issues
- :delete_issues
- :manage_public_queries
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index df7123879..de3553173 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -14,6 +14,7 @@ users_004: auth_source_id:
mail_notification: true
login: rhill
+ type: User
users_001:
created_on: 2006-07-19 19:12:21 +02:00
status: 1
@@ -29,6 +30,7 @@ users_001: auth_source_id:
mail_notification: true
login: admin
+ type: User
users_002:
created_on: 2006-07-19 19:32:09 +02:00
status: 1
@@ -44,6 +46,7 @@ users_002: auth_source_id:
mail_notification: true
login: jsmith
+ type: User
users_003:
created_on: 2006-07-19 19:33:19 +02:00
status: 1
@@ -59,6 +62,7 @@ users_003: auth_source_id:
mail_notification: true
login: dlopper
+ type: User
users_005:
id: 5
created_on: 2006-07-19 19:33:19 +02:00
@@ -75,3 +79,22 @@ users_005: auth_source_id:
mail_notification: true
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
+
+
\ No newline at end of file diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 06d2f1029..d60e32200 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -32,7 +32,8 @@ class IssuesControllerTest < Test::Unit::TestCase :issue_categories, :enabled_modules, :enumerations, - :attachments + :attachments, + :workflows def setup @controller = IssuesController.new @@ -94,13 +95,37 @@ class IssuesControllerTest < Test::Unit::TestCase assert_equal 'application/atom+xml', @response.content_type end - def test_show + def test_show_by_anonymous get :show, :id => 1 assert_response :success assert_template 'show.rhtml' 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 + def test_get_edit @request.session[:user_id] = 2 get :edit, :id => 1 @@ -129,21 +154,100 @@ class IssuesControllerTest < Test::Unit::TestCase assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}") 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) assert_equal 1, issue.status_id @request.session[:user_id] = 2 - post :change_status, :id => 1, - :new_status_id => 2, - :issue => { :assigned_to_id => 3 }, - :notes => 'Assigned to dlopper', - :confirm => 1 + post :update, + :id => 1, + :issue => { :status_id => 2, :assigned_to_id => 3 }, + :notes => 'Assigned to dlopper' assert_redirected_to 'issues/show/1' issue.reload 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 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 def test_context_menu diff --git a/test/integration/issues_test.rb b/test/integration/issues_test.rb index 702fbc026..a9d3f9c74 100644 --- a/test/integration/issues_test.rb +++ b/test/integration/issues_test.rb @@ -38,7 +38,9 @@ class IssuesTest < ActionController::IntegrationTest def test_issue_attachements 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" # make sure attachment was saved diff --git a/test/test_helper.rb b/test/test_helper.rb index 542d4ce72..7c81c3607 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -53,6 +53,10 @@ class Test::Unit::TestCase assert_redirected_to "my/page" assert_equal login, User.find(session[:user_id]).login end + + def test_uploaded_file(name, mime) + ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + "/files/#{name}", mime) + end end @@ -70,4 +74,4 @@ class String def read self.to_s end -end
\ No newline at end of file +end diff --git a/vendor/plugins/gloc-1.1.0/lib/gloc-rails.rb b/vendor/plugins/gloc-1.1.0/lib/gloc-rails.rb index 8f201bcb8..aa65991b0 100644 --- a/vendor/plugins/gloc-1.1.0/lib/gloc-rails.rb +++ b/vendor/plugins/gloc-1.1.0/lib/gloc-rails.rb @@ -155,6 +155,27 @@ module ActiveRecord #:nodoc: # 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 ClassMethods
# The default Rails version of this function creates an error message and then
|