]> source.dussan.org Git - redmine.git/commitdiff
Merged IssuesController change_status and add_note actions.
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 6 Jan 2008 17:06:14 +0000 (17:06 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 6 Jan 2008 17:06:14 +0000 (17:06 +0000)
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

39 files changed:
app/controllers/issues_controller.rb
app/controllers/projects_controller.rb
app/models/issue.rb
app/models/issue_status.rb
app/views/issues/_bulk_edit_form.rhtml
app/views/issues/_update.rhtml [new file with mode: 0644]
app/views/issues/change_status.rhtml [deleted file]
app/views/issues/context_menu.rhtml
app/views/issues/show.rhtml
app/views/issues/update.rhtml [new file with mode: 0644]
lang/bg.yml
lang/cs.yml
lang/de.yml
lang/en.yml
lang/es.yml
lang/fr.yml
lang/he.yml
lang/it.yml
lang/ja.yml
lang/ko.yml
lang/nl.yml
lang/pl.yml
lang/pt-br.yml
lang/pt.yml
lang/ro.yml
lang/ru.yml
lang/sr.yml
lang/sv.yml
lang/zh-tw.yml
lang/zh.yml
lib/redmine.rb
lib/redmine/default_data/loader.rb
test/fixtures/enumerations.yml
test/fixtures/roles.yml
test/fixtures/users.yml
test/functional/issues_controller_test.rb
test/integration/issues_test.rb
test/test_helper.rb
vendor/plugins/gloc-1.1.0/lib/gloc-rails.rb

index 39a1e2d130158fe52bea93089a46c64d8f8dcef0..11112289e7cd184382ff54769d469a2a0c9a5f8f 100644 (file)
@@ -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)),
index a58eef7194ebdcf4459bbc07b2bac080cea31cd1..2ae742dfa46b87233d228db0619cbbc8a15c79cd 100644 (file)
@@ -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'
index f7b01ea6a13a53264fcd6db2626d49699dba9d39..419c6cdc7253cc59225088b3f73af91153848cdf 100644 (file)
@@ -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
index a5d228405d7febd2411bf3e507192052c6c49324..ddff9c0050d5aa83a08bb84cd4b0d8be603848ae 100644 (file)
@@ -56,6 +56,10 @@ class IssueStatus < ActiveRecord::Base
       false
   end
 
+  def <=>(status)
+    position <=> status.position
+  end
+  
   def to_s; name end
 
 private
index bc3f62e6d50cdffa270ab14c6a2c2302b8c13441..e9e1cef86be04265aa5efa91f70e953926ba6610 100644 (file)
@@ -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 (file)
index 0000000..3cf5938
--- /dev/null
@@ -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 (file)
index a1e2945..0000000
+++ /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 %>
index 3af49fb040153d8ae5aca1a1a337b165d7b565ab..0f11bb943387d931d586a857f73c34e2569f3f69 100644 (file)
@@ -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>
                <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},
index 91b638216a198099703482a10699939a15f0bac6..8cd44424cdd5c54bd46a7edcbad8ab1f72af2f79 100644 (file)
@@ -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 (file)
index 0000000..44e72da
--- /dev/null
@@ -0,0 +1,4 @@
+<h2><%= @issue.tracker.name %> #<%= @issue.id %>: <%=h @issue.subject %></h2>
+
+<%= error_messages_for 'issue' %>
+<%= render :partial => 'update' %>
index eeb6ef80b9d93f6cfab7c08a5fab5df250935193..57e7fd22dfa87844e4a67fc6b30e5dc97ee08551 100644 (file)
@@ -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
index e99fe061bc28beef67738cd09b1ece544b80ca27..5cbf1133d4c13a19d0be91460ca449f3620c04ab 100644 (file)
@@ -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
index cd4e222aed59dfd8d3d68491a26c131c55433cf9..ba3dcda5bed75fd306ff5cda2ea7dc933ee0e0d6 100644 (file)
@@ -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
index 7a1a2622fe2106f0c550f2806ddf951b6e569ea4..eae61d7bff0c2f31ee66f30f152b824991cef130 100644 (file)
@@ -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
index bae2955862e6a963dd91247bc91e4676640cb2e8..1a3af0f5cea2d5575ea82850d7c73afb0a75f811 100644 (file)
@@ -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
index d47feceb2dfcab0d5801098ed941762db42e8e2f..ff2bf792f08afa327a5423f9e7a9a322f95fab21 100644 (file)
@@ -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é
index e2277dc3dde43fff1dab842dc2f83023973097da..d110ee8bfa7bdf4c7d0e556ff762b652182b2ab2 100644 (file)
@@ -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
index 709f4177448d45b74e264847961f4f291690b00b..bb9440ffd139e89ffe1ffb27be2b11d8761b61f0 100644 (file)
@@ -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
index 286fb59719f71e6653e17722bd8fb5de0d634d60..e4c13b34cbbd8990b248f30fea54675aad91711d 100644 (file)
@@ -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
index 52c9b5431fdcb96c1e66536842198eed3c89b946..790598b8d6331035eb4ae6ba2533c08668ecc670 100644 (file)
@@ -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
index 43e1704a4c0affad2667f15fef1509fee2623170..2e18a6b7c4053e9ea6f7d4dd3326ff0a8132aae3 100644 (file)
@@ -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
index 77ae6bb66b0a9d4e006d2ad6838854c10d0b9b5c..0ee0e37929eea64b0a647621894d28a25a0c629f 100644 (file)
@@ -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
index 55e8b3faea0088ba2fd8d5ee794b173acd6796f7..21d21b793653a4130c39dade658251a71d64a703 100644 (file)
@@ -557,3 +557,5 @@ notice_default_data_loaded: Default configuration successfully loaded.
 text_load_default_configuration: Load the default configuration\r
 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."\r
 error_can_t_load_default_data: "Default configuration could not be loaded: %s"\r
+button_update: Update\r
+label_change_properties: Change properties\r
index 0a5661997ff994a2a7c2af38e814083da999dd69..c7f05f8c8e8f05bc3ef6729ea54b463421f0d0a0 100644 (file)
@@ -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
index 1381adba9e27e42eb4ed69374f5632bd318a6747..6ebc3b0e59836add5bc9c6715cd65ac89fb7761b 100644 (file)
@@ -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
index b293dc309850fb91c4cbe88a0a246d38e4ba999a..569e96884c595b297e2aba223e9c50c35bfb7352 100644 (file)
@@ -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
index c842ec43886f03623fde8c68a0695d9e59be3e54..1d4f52539b18fe8bb2096d0771c67f0e9d46a073 100644 (file)
@@ -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
index 85fb19bcb03dd61e9e35a6f65e5eeef5262c2909..75f297c2046dfe8bf0b55105911fafb24d2c1b4b 100644 (file)
@@ -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
index 719a2e868bf547945eb8378f9da5f16f82834aec..5bf47d943a892ee3f11147465ff857b03e3852f1 100644 (file)
@@ -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
index 05134751f76cd66b0b3811bae0a33ae2dbb34451..16f4c71959d66b4ee89478ccb9d3d8ec46ff6a37 100644 (file)
@@ -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
index 9b29257bd88a8863cc09791bfa176a487dd72042..2dca9ed50016e9734d01fd0ccfbc866b4d590ed2 100644 (file)
@@ -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
index 0abb5572a647cc135e836fae86f8b386a4c7c8a5..11bd2a0b4086038f60a51945384bfe8d0f2a600c 100644 (file)
@@ -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,
index eeef99b5b2f51764686f8cf21bb6de145901d777..c90a997ee15b88c12366e1f376ea6359f6b1f1da 100644 (file)
@@ -31,3 +31,12 @@ enumerations_008:
   name: Immediate\r
   id: 8\r
   opt: IPRI\r
+enumerations_009: \r
+  name: Design\r
+  id: 9\r
+  opt: ACTI\r
+enumerations_010: \r
+  name: Development\r
+  id: 10\r
+  opt: ACTI\r
+  
\ No newline at end of file
index a089a98f9fe7b6ae52a2e5152ff0163eedbb8e58..c4d417a0990bb8d771fe94ba6e80ceece6cb01f8 100644 (file)
@@ -9,7 +9,6 @@ roles_004:
     - :edit_issues\r
     - :manage_issue_relations\r
     - :add_issue_notes\r
-    - :change_issue_status\r
     - :move_issues\r
     - :save_queries\r
     - :view_gantt\r
@@ -34,6 +33,7 @@ roles_005:
   builtin: 2\r
   permissions: |\r
     --- \r
+    - :add_issue_notes\r
     - :view_gantt\r
     - :view_calendar\r
     - :view_time_entries\r
@@ -58,7 +58,6 @@ roles_001:
     - :edit_issues\r
     - :manage_issue_relations\r
     - :add_issue_notes\r
-    - :change_issue_status\r
     - :move_issues\r
     - :delete_issues\r
     - :manage_public_queries\r
@@ -99,7 +98,6 @@ roles_002:
     - :edit_issues\r
     - :manage_issue_relations\r
     - :add_issue_notes\r
-    - :change_issue_status\r
     - :move_issues\r
     - :delete_issues\r
     - :manage_public_queries\r
@@ -137,7 +135,6 @@ roles_003:
     - :edit_issues\r
     - :manage_issue_relations\r
     - :add_issue_notes\r
-    - :change_issue_status\r
     - :move_issues\r
     - :delete_issues\r
     - :manage_public_queries\r
index df712387978a47e9a98fb299edf67865e7b3bfe1..de35531739f81efcad6f2fa8cf42e6118ddf4ec9 100644 (file)
@@ -14,6 +14,7 @@ users_004:
   auth_source_id: \r
   mail_notification: true\r
   login: rhill\r
+  type: User\r
 users_001: \r
   created_on: 2006-07-19 19:12:21 +02:00\r
   status: 1\r
@@ -29,6 +30,7 @@ users_001:
   auth_source_id: \r
   mail_notification: true\r
   login: admin\r
+  type: User\r
 users_002: \r
   created_on: 2006-07-19 19:32:09 +02:00\r
   status: 1\r
@@ -44,6 +46,7 @@ users_002:
   auth_source_id: \r
   mail_notification: true\r
   login: jsmith\r
+  type: User\r
 users_003: \r
   created_on: 2006-07-19 19:33:19 +02:00\r
   status: 1\r
@@ -59,6 +62,7 @@ users_003:
   auth_source_id: \r
   mail_notification: true\r
   login: dlopper\r
+  type: User\r
 users_005: \r
   id: 5\r
   created_on: 2006-07-19 19:33:19 +02:00\r
@@ -75,3 +79,22 @@ users_005:
   auth_source_id: \r
   mail_notification: true\r
   login: dlopper2\r
+  type: User\r
+users_006: \r
+  id: 6\r
+  created_on: 2006-07-19 19:33:19 +02:00\r
+  status: 1\r
+  last_login_on: \r
+  language: ''\r
+  hashed_password: 1\r
+  updated_on: 2006-07-19 19:33:19 +02:00\r
+  admin: false\r
+  mail: ''\r
+  lastname: Anonymous\r
+  firstname: ''\r
+  auth_source_id: \r
+  mail_notification: false\r
+  login: ''\r
+  type: AnonymousUser\r
+\r
+  
\ No newline at end of file
index 06d2f1029db6424f74a11f87f6f6d5ac8a60727b..d60e32200504fc184d24168a7149b16ee3f2d635 100644 (file)
@@ -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
index 702fbc02654d325a39fb6c015b47a04f6ef038ab..a9d3f9c7448f22e9a35668e67e450d243d253bc7 100644 (file)
@@ -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
index 542d4ce72478b930512cf117c2104c0359adbea7..7c81c3607d5a3d06f69b709ee44f65ddbc31d6d3 100644 (file)
@@ -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
index 8f201bcb89aa01ea1f53c3782b3db2ac699036fb..aa65991b0ebf914663b189d3fd7c4b315facb8be 100644 (file)
@@ -155,6 +155,27 @@ module ActiveRecord #:nodoc:
 #    end\r
 #  end\r
   \r
+  class Errors\r
+    include GLoc\r
+    \r
+    def full_messages\r
+      full_messages = []\r
+\r
+      @errors.each_key do |attr|\r
+        @errors[attr].each do |msg|\r
+          next if msg.nil?\r
+\r
+          if attr == "base"\r
+            full_messages << (msg.is_a?(Symbol) ? l(msg) : msg)\r
+          else\r
+            full_messages << @base.class.human_attribute_name(attr) + " " + (msg.is_a?(Symbol) ? l(msg) : msg)\r
+          end\r
+        end\r
+      end\r
+      full_messages\r
+    end\r
+  end\r
+  \r
   module Validations #:nodoc:\r
     module ClassMethods\r
       # The default Rails version of this function creates an error message and then\r