##// END OF EJS Templates
improved issues change history...
Jean-Philippe Lang -
r52:42181112ff73
parent child
Show More
@@ -0,0 +1,22
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class Journal < ActiveRecord::Base
19 belongs_to :journalized, :polymorphic => true
20 belongs_to :user
21 has_many :details, :class_name => "JournalDetail", :dependent => true
22 end
@@ -0,0 +1,20
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class JournalDetail < ActiveRecord::Base
19 belongs_to :journal
20 end
@@ -0,0 +1,11
1 <% for journal in journals %>
2 <h4><%= format_time(journal.created_on) %> - <%= journal.user.name %></h4>
3 <ul>
4 <% for detail in journal.details %>
5 <li><%= show_detail(detail) %></li>
6 <% end %>
7 </ul>
8 <% if journal.notes? %>
9 <%= simple_format auto_link journal.notes %>
10 <% end %>
11 <% end %>
@@ -0,0 +1,6
1 <h3><%=l(:label_history)%></h3>
2 <div id="history">
3 <%= render :partial => 'history', :locals => { :journals => @journals } %>
4 </div>
5 <br />
6 <p><%= link_to l(:button_back), :action => 'show', :id => @issue %></p> No newline at end of file
@@ -0,0 +1,8
1 Issue #<%= @issue.id %> has been updated.
2 <%= @journal.user.name %>
3 <% for detail in @journal.details %>
4 <%= show_detail(detail) %>
5 <% end %>
6 <%= @journal.notes if @journal.notes? %>
7 ----------------------------------------
8 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,8
1 Issue #<%= @issue.id %> has been updated.
2 <%= @journal.user.name %>
3 <% for detail in @journal.details %>
4 <%= show_detail(detail) %>
5 <% end %>
6 <%= @journal.notes if @journal.notes? %>
7 ----------------------------------------
8 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,8
1 Issue #<%= @issue.id %> has been updated.
2 <%= @journal.user.name %>
3 <% for detail in @journal.details %>
4 <%= show_detail(detail) %>
5 <% end %>
6 <%= @journal.notes if @journal.notes? %>
7 ----------------------------------------
8 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,8
1 La demande #<%= @issue.id %> a été mise à jour.
2 <%= @journal.user.name %> - <%= format_date(@journal.created_on) %>
3 <% for detail in @journal.details %>
4 <%= show_detail(detail) %>
5 <% end %>
6 <%= journal.notes if journal.notes? %>
7 ----------------------------------------
8 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,54
1 class CreateJournals < ActiveRecord::Migration
2
3 # model removed, but needed for data migration
4 class IssueHistory < ActiveRecord::Base; belongs_to :issue; end
5
6 def self.up
7 create_table :journals, :force => true do |t|
8 t.column "journalized_id", :integer, :default => 0, :null => false
9 t.column "journalized_type", :string, :limit => 30, :default => "", :null => false
10 t.column "user_id", :integer, :default => 0, :null => false
11 t.column "notes", :text
12 t.column "created_on", :datetime, :null => false
13 end
14 create_table :journal_details, :force => true do |t|
15 t.column "journal_id", :integer, :default => 0, :null => false
16 t.column "property", :string, :limit => 30, :default => "", :null => false
17 t.column "prop_key", :string, :limit => 30, :default => "", :null => false
18 t.column "old_value", :string
19 t.column "value", :string
20 end
21
22 # indexes
23 add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id"
24 add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id"
25
26 Permission.create :controller => "issues", :action => "history", :description => "label_history", :sort => 1006, :is_public => true, :mail_option => 0, :mail_enabled => 0
27
28 # data migration
29 IssueHistory.find(:all, :include => :issue).each {|h|
30 j = Journal.new(:journalized => h.issue, :user_id => h.author_id, :notes => h.notes, :created_on => h.created_on)
31 j.details << JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :value => h.status_id)
32 j.save
33 }
34
35 drop_table :issue_histories
36 end
37
38 def self.down
39 drop_table :journal_details
40 drop_table :journals
41
42 create_table "issue_histories", :force => true do |t|
43 t.column "issue_id", :integer, :default => 0, :null => false
44 t.column "status_id", :integer, :default => 0, :null => false
45 t.column "author_id", :integer, :default => 0, :null => false
46 t.column "notes", :text, :default => ""
47 t.column "created_on", :timestamp
48 end
49
50 add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id"
51
52 Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'history']).destroy
53 end
54 end
@@ -41,7 +41,7 class ApplicationController < ActionController::Base
41 if self.logged_in_user and self.logged_in_user.language and !self.logged_in_user.language.empty? and GLoc.valid_languages.include? self.logged_in_user.language.to_sym
41 if self.logged_in_user and self.logged_in_user.language and !self.logged_in_user.language.empty? and GLoc.valid_languages.include? self.logged_in_user.language.to_sym
42 self.logged_in_user.language
42 self.logged_in_user.language
43 elsif request.env['HTTP_ACCEPT_LANGUAGE']
43 elsif request.env['HTTP_ACCEPT_LANGUAGE']
44 accept_lang = HTTPUtils.parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
44 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
45 if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
45 if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
46 accept_lang
46 accept_lang
47 end
47 end
@@ -104,4 +104,23 class ApplicationController < ActionController::Base
104 session[:return_to] = nil
104 session[:return_to] = nil
105 end
105 end
106 end
106 end
107
108 # qvalues http header parser
109 # code taken from webrick
110 def parse_qvalues(value)
111 tmp = []
112 if value
113 parts = value.split(/,\s*/)
114 parts.each {|part|
115 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
116 val = m[1]
117 q = (m[2] or 1).to_f
118 tmp.push([val, q])
119 end
120 }
121 tmp = tmp.sort_by{|val, q| -q}
122 tmp.collect!{|val, q| val}
123 end
124 return tmp
125 end
107 end No newline at end of file
126 end
@@ -27,6 +27,13 class IssuesController < ApplicationController
27 def show
27 def show
28 @status_options = @issue.status.workflows.find(:all, :include => :new_status, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
28 @status_options = @issue.status.workflows.find(:all, :include => :new_status, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
29 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
29 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
30 @journals_count = @issue.journals.count
31 @journals = @issue.journals.find(:all, :include => [:user, :details], :limit => 15, :order => "journals.created_on desc")
32 end
33
34 def history
35 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "journals.created_on desc")
36 @journals_count = @journals.length
30 end
37 end
31
38
32 def export_pdf
39 def export_pdf
@@ -41,6 +48,7 class IssuesController < ApplicationController
41 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
48 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
42 else
49 else
43 begin
50 begin
51 @issue.init_journal(self.logged_in_user)
44 # Retrieve custom fields and values
52 # Retrieve custom fields and values
45 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
53 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
46 @issue.custom_values = @custom_values
54 @issue.custom_values = @custom_values
@@ -57,13 +65,14 class IssuesController < ApplicationController
57 end
65 end
58
66
59 def add_note
67 def add_note
60 unless params[:history][:notes].empty?
68 unless params[:notes].empty?
61 @history = @issue.histories.build(params[:history])
69 journal = @issue.init_journal(self.logged_in_user, params[:notes])
62 @history.author_id = self.logged_in_user.id if self.logged_in_user
70 #@history = @issue.histories.build(params[:history])
63 @history.status = @issue.status
71 #@history.author_id = self.logged_in_user.id if self.logged_in_user
64 if @history.save
72 #@history.status = @issue.status
73 if @issue.save
65 flash[:notice] = l(:notice_successful_update)
74 flash[:notice] = l(:notice_successful_update)
66 Mailer.deliver_issue_add_note(@history) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
75 Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
67 redirect_to :action => 'show', :id => @issue
76 redirect_to :action => 'show', :id => @issue
68 return
77 return
69 end
78 end
@@ -73,17 +82,20 class IssuesController < ApplicationController
73 end
82 end
74
83
75 def change_status
84 def change_status
76 @history = @issue.histories.build(params[:history])
85 #@history = @issue.histories.build(params[:history])
77 @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
86 @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
87 @new_status = IssueStatus.find(params[:new_status_id])
78 if params[:confirm]
88 if params[:confirm]
79 begin
89 begin
80 @history.author_id = self.logged_in_user.id if self.logged_in_user
90 #@history.author_id = self.logged_in_user.id if self.logged_in_user
81 @issue.status = @history.status
91 #@issue.status = @history.status
82 @issue.fixed_version_id = (params[:issue][:fixed_version_id])
92 #@issue.fixed_version_id = (params[:issue][:fixed_version_id])
83 @issue.assigned_to_id = (params[:issue][:assigned_to_id])
93 #@issue.assigned_to_id = (params[:issue][:assigned_to_id])
84 @issue.done_ratio = (params[:issue][:done_ratio])
94 #@issue.done_ratio = (params[:issue][:done_ratio])
85 @issue.lock_version = (params[:issue][:lock_version])
95 #@issue.lock_version = (params[:issue][:lock_version])
86 if @issue.save
96 @issue.init_journal(self.logged_in_user, params[:notes])
97 @issue.status = @new_status
98 if @issue.update_attributes(params[:issue])
87 flash[:notice] = l(:notice_successful_update)
99 flash[:notice] = l(:notice_successful_update)
88 Mailer.deliver_issue_change_status(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
100 Mailer.deliver_issue_change_status(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
89 redirect_to :action => 'show', :id => @issue
101 redirect_to :action => 'show', :id => @issue
@@ -28,6 +28,7 class ProjectsController < ApplicationController
28 include CustomFieldsHelper
28 include CustomFieldsHelper
29 helper :ifpdf
29 helper :ifpdf
30 include IfpdfHelper
30 include IfpdfHelper
31 helper IssuesHelper
31
32
32 def index
33 def index
33 list
34 list
@@ -81,7 +81,7 module ApplicationHelper
81 end
81 end
82
82
83 def textilizable(text)
83 def textilizable(text)
84 $RDM_TEXTILE_DISABLED ? text : textilize(text)
84 $RDM_TEXTILE_DISABLED ? text : RedCloth.new(text).to_html
85 end
85 end
86
86
87 def error_messages_for(object_name, options = {})
87 def error_messages_for(object_name, options = {})
@@ -54,15 +54,20 module CustomFieldsHelper
54 # Return a string used to display a custom value
54 # Return a string used to display a custom value
55 def show_value(custom_value)
55 def show_value(custom_value)
56 return "" unless custom_value
56 return "" unless custom_value
57
57 format_value(custom_value.value, custom_value.custom_field.field_format)
58 case custom_value.custom_field.field_format
58 end
59
60 # Return a string used to display a custom value
61 def format_value(value, field_format)
62 return "" unless value
63 case field_format
59 when "date"
64 when "date"
60 custom_value.value.empty? ? "" : l_date(custom_value.value.to_date)
65 value.empty? ? "" : l_date(value.to_date)
61 when "bool"
66 when "bool"
62 l_YesNo(custom_value.value == "1")
67 l_YesNo(value == "1")
63 else
68 else
64 custom_value.value
69 value
65 end
70 end
66 end
71 end
67
72
68 # Return an array of custom field formats which can be used in select_tag
73 # Return an array of custom field formats which can be used in select_tag
@@ -25,12 +25,12 module IfpdfHelper
25
25
26 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
26 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
27 @ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
27 @ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
28 super w,h,@ic.iconv(txt),border,ln,align,fill,link
28 txt = begin
29 end
29 @ic.iconv(txt)
30
30 rescue
31 def MultiCell(w,h,txt,border=0,align='J',fill=0)
31 txt
32 @ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
32 end
33 super w,h,txt,border,align,fill
33 super w,h,txt,border,ln,align,fill,link
34 end
34 end
35
35
36 def Footer
36 def Footer
@@ -15,5 +15,60
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module IssuesHelper
18 module IssuesHelper
19
20 def show_detail(detail, no_html=false)
21 case detail.property
22 when 'attr'
23 label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
24 case detail.prop_key
25 when 'due_date', 'start_date'
26 value = format_date(detail.value.to_date) if detail.value
27 old_value = format_date(detail.old_value.to_date) if detail.old_value
28 when 'status_id'
29 s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
30 s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
31 when 'assigned_to_id'
32 u = User.find_by_id(detail.value) and value = u.name if detail.value
33 u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
34 when 'priority_id'
35 e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
36 e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
37 when 'category_id'
38 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
39 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
40 when 'fixed_version_id'
41 v = Version.find_by_id(detail.value) and value = v.name if detail.value
42 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
43 end
44 when 'cf'
45 custom_field = CustomField.find_by_id(detail.prop_key)
46 if custom_field
47 label = custom_field.name
48 value = format_value(detail.value, custom_field.field_format) if detail.value
49 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
50 end
51 end
52
53 label ||= detail.prop_key
54 value ||= detail.value
55 old_value ||= detail.old_value
56
57 unless no_html
58 label = content_tag('strong', label)
59 old_value = content_tag("i", old_value) if old_value
60 old_value = content_tag("strike", old_value) if old_value and !value
61 value = content_tag("i", value) if value
62 end
63
64 if value
65 if old_value
66 label + " " + l(:text_journal_changed, old_value, value)
67 else
68 label + " " + l(:text_journal_set_to, value)
69 end
70 else
71 label + " " + l(:text_journal_deleted) + " (#{old_value})"
72 end
73 end
19 end
74 end
@@ -26,7 +26,8 class Issue < ActiveRecord::Base
26 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
27 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
28
28
29 has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
29 #has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
30 has_many :journals, :as => :journalized, :dependent => true
30 has_many :attachments, :as => :container, :dependent => true
31 has_many :attachments, :as => :container, :dependent => true
31
32
32 has_many :custom_values, :dependent => true, :as => :customized
33 has_many :custom_values, :dependent => true, :as => :customized
@@ -51,8 +52,28 class Issue < ActiveRecord::Base
51 end
52 end
52 end
53 end
53
54
54 def before_create
55 #def before_create
55 build_history
56 # build_history
57 #end
58
59 def before_save
60 if @current_journal
61 # attributes changes
62 (Issue.column_names - %w(id description)).each {|c|
63 @current_journal.details << JournalDetail.new(:property => 'attr',
64 :prop_key => c,
65 :old_value => @issue_before_change.send(c),
66 :value => send(c)) unless send(c)==@issue_before_change.send(c)
67 }
68 # custom fields changes
69 custom_values.each {|c|
70 @current_journal.details << JournalDetail.new(:property => 'cf',
71 :prop_key => c.custom_field_id,
72 :old_value => @custom_values_before_change[c.custom_field_id],
73 :value => c.value) unless @custom_values_before_change[c.custom_field_id]==c.value
74 }
75 @current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
76 end
56 end
77 end
57
78
58 def long_id
79 def long_id
@@ -63,12 +84,20 class Issue < ActiveRecord::Base
63 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
84 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
64 return nil
85 return nil
65 end
86 end
87
88 def init_journal(user, notes = "")
89 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
90 @issue_before_change = self.clone
91 @custom_values_before_change = {}
92 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
93 @current_journal
94 end
66
95
67 private
96 private
68 # Creates an history for the issue
97 # Creates an history for the issue
69 def build_history
98 #def build_history
70 @history = self.histories.build
99 # @history = self.histories.build
71 @history.status = self.status
100 # @history.status = self.status
72 @history.author = self.author
101 # @history.author = self.author
73 end
102 #end
74 end
103 end
@@ -17,14 +17,6
17
17
18 class Mailer < ActionMailer::Base
18 class Mailer < ActionMailer::Base
19
19
20 def issue_change_status(issue)
21 # Sends to all project members
22 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
23 @from = $RDM_MAIL_FROM
24 @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
25 @body['issue'] = issue
26 end
27
28 def issue_add(issue)
20 def issue_add(issue)
29 # Sends to all project members
21 # Sends to all project members
30 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
22 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
@@ -33,12 +25,14 class Mailer < ActionMailer::Base
33 @body['issue'] = issue
25 @body['issue'] = issue
34 end
26 end
35
27
36 def issue_add_note(history)
28 def issue_edit(journal)
37 # Sends to all project members
29 # Sends to all project members
38 @recipients = history.issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
30 issue = journal.journalized
31 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
39 @from = $RDM_MAIL_FROM
32 @from = $RDM_MAIL_FROM
40 @subject = "[#{history.issue.project.name} - #{history.issue.tracker.name} ##{history.issue.id}] #{history.issue.status.name} - #{history.issue.subject}"
33 @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
41 @body['history'] = history
34 @body['issue'] = issue
35 @body['journal']= journal
42 end
36 end
43
37
44 def lost_password(token)
38 def lost_password(token)
@@ -66,29 +66,34
66 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
66 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
67
67
68 pdf.Ln
68 pdf.Ln
69
69 pdf.SetFont('Arial','B',9)
70 pdf.SetFont('Arial','B',9)
70 pdf.Cell(190,5, l(:label_history),"B")
71 pdf.Cell(190,5, l(:label_history), "B")
71 pdf.Ln
72 pdf.Ln
72 for history in issue.histories.find(:all, :include => [:author, :status])
73 for journal in issue.journals.find(:all, :include => :user, :order => "journals.created_on desc")
73 pdf.SetFont('Arial','B',8)
74 pdf.SetFont('Arial','B',8)
74 pdf.Cell(100,5, history.status.name)
75 pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
75 pdf.SetFont('Arial','',8)
76 pdf.Cell(20,5, format_date(history.created_on))
77 pdf.Cell(70,5, history.author.name,0,0,"R")
78 pdf.SetFont('Arial','',8)
79 pdf.Ln
76 pdf.Ln
80 pdf.Cell(10,4, "") and pdf.MultiCell(180,4, history.notes) if history.notes?
77 pdf.SetFont('Arial','I',8)
78 for detail in journal.details
79 pdf.Cell(190,5, "- " + show_detail(detail, true))
80 pdf.Ln
81 end
82 if journal.notes?
83 pdf.SetFont('Arial','',8)
84 pdf.MultiCell(190,5, journal.notes)
85 end
86 pdf.Ln
81 end
87 end
82 pdf.Ln
88
83
84 pdf.SetFont('Arial','B',9)
89 pdf.SetFont('Arial','B',9)
85 pdf.Cell(190,5, l(:label_attachment_plural), "B")
90 pdf.Cell(190,5, l(:label_attachment_plural), "B")
86 pdf.Ln
91 pdf.Ln
87 for attachment in issue.attachments
92 for attachment in issue.attachments
88 pdf.SetFont('Arial','',8)
93 pdf.SetFont('Arial','',8)
89 pdf.Cell(80,5, attachment.filename)
94 pdf.Cell(80,5, attachment.filename)
90 pdf.Cell(20,5, human_size(attachment.filesize))
95 pdf.Cell(20,5, human_size(attachment.filesize),0,0,"R")
91 pdf.Cell(20,5, format_date(attachment.created_on))
96 pdf.Cell(20,5, format_date(attachment.created_on),0,0,"R")
92 pdf.Cell(70,5, attachment.author.name,0,0,"R")
97 pdf.Cell(70,5, attachment.author.name,0,0,"R")
93 pdf.Ln
98 pdf.Ln
94 end
99 end
@@ -1,13 +1,13
1 <h2><%=l(:label_issue)%> #<%= @issue.id %>: <%= @issue.subject %></h2>
1 <h2><%=l(:label_issue)%> #<%= @issue.id %>: <%= @issue.subject %></h2>
2
2
3 <%= error_messages_for 'history' %>
3 <%= error_messages_for 'issue' %>
4 <%= start_form_tag({:action => 'change_status', :id => @issue}, :class => "tabular") %>
4 <%= start_form_tag({:action => 'change_status', :id => @issue}, :class => "tabular") %>
5
5
6 <%= hidden_field_tag 'confirm', 1 %>
6 <%= hidden_field_tag 'confirm', 1 %>
7 <%= hidden_field 'history', 'status_id' %>
7 <%= hidden_field_tag 'new_status_id', @new_status.id %>
8
8
9 <div class="box">
9 <div class="box">
10 <p><label><%=l(:label_issue_status_new)%></label> <%= @history.status.name %></p>
10 <p><label><%=l(:label_issue_status_new)%></label> <%= @new_status.name %></p>
11
11
12 <p><label for="issue_assigned_to_id"><%=l(:field_assigned_to)%></label>
12 <p><label for="issue_assigned_to_id"><%=l(:field_assigned_to)%></label>
13 <select name="issue[assigned_to_id]">
13 <select name="issue[assigned_to_id]">
@@ -25,12 +25,13
25 <select name="issue[fixed_version_id]">
25 <select name="issue[fixed_version_id]">
26 <option value="">--none--</option>
26 <option value="">--none--</option>
27 <%= options_from_collection_for_select @issue.project.versions, "id", "name", @issue.fixed_version_id %>
27 <%= options_from_collection_for_select @issue.project.versions, "id", "name", @issue.fixed_version_id %>
28 </select></p>
28 </select></p>
29
30 <p><label for="history_notes"><%=l(:field_notes)%></label>
31 <%= text_area 'history', 'notes', :cols => 60, :rows => 10 %></p>
32 </div>
33
29
30 <p><label for="notes"><%= l(:field_notes) %></label>
31 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10 %></p>
32
33 </div>
34
34 <%= hidden_field 'issue', 'lock_version' %>
35 <%= hidden_field 'issue', 'lock_version' %>
35 <%= submit_tag l(:button_save) %>
36 <%= submit_tag l(:button_save) %>
36 <%= end_form_tag %>
37 <%= end_form_tag %>
@@ -19,7 +19,7
19
19
20 <div class="clear">
20 <div class="clear">
21 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
21 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
22 <p><%= f.text_area :description, :cols => 60, :rows => 10, :required => true %></p>
22 <p><%= f.text_area :description, :cols => 60, :rows => [[10, @issue.description.length / 50].max, 100].min, :required => true %></p>
23
23
24 <% for @custom_value in @custom_values %>
24 <% for @custom_value in @custom_values %>
25 <p><%= custom_field_tag_with_label @custom_value %></p>
25 <p><%= custom_field_tag_with_label @custom_value %></p>
@@ -44,8 +44,8 end %>
44
44
45 <b><%=l(:field_description)%> :</b><br /><br />
45 <b><%=l(:field_description)%> :</b><br /><br />
46 <%= textilizable @issue.description %>
46 <%= textilizable @issue.description %>
47
47 <br />
48 <p>
48 <div style="float:left;">
49 <% if authorize_for('issues', 'edit') %>
49 <% if authorize_for('issues', 'edit') %>
50 <%= start_form_tag ({:controller => 'issues', :action => 'edit', :id => @issue}, :method => "get" ) %>
50 <%= start_form_tag ({:controller => 'issues', :action => 'edit', :id => @issue}, :method => "get" ) %>
51 <%= submit_tag l(:button_edit) %>
51 <%= submit_tag l(:button_edit) %>
@@ -56,7 +56,7 end %>
56 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
56 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
57 <%= start_form_tag ({:controller => 'issues', :action => 'change_status', :id => @issue}) %>
57 <%= start_form_tag ({:controller => 'issues', :action => 'change_status', :id => @issue}) %>
58 <%=l(:label_change_status)%> :
58 <%=l(:label_change_status)%> :
59 <select name="history[status_id]">
59 <select name="new_status_id">
60 <%= options_from_collection_for_select @status_options, "id", "name" %>
60 <%= options_from_collection_for_select @status_options, "id", "name" %>
61 </select>
61 </select>
62 <%= submit_tag l(:button_change) %>
62 <%= submit_tag l(:button_change) %>
@@ -71,30 +71,25 end %>
71 <%= end_form_tag %>
71 <%= end_form_tag %>
72 &nbsp;&nbsp;
72 &nbsp;&nbsp;
73 <% end %>
73 <% end %>
74
74 </div>
75 <div style="float:right;">
75 <% if authorize_for('issues', 'destroy') %>
76 <% if authorize_for('issues', 'destroy') %>
76 <%= start_form_tag ({:controller => 'issues', :action => 'destroy', :id => @issue} ) %>
77 <%= start_form_tag ({:controller => 'issues', :action => 'destroy', :id => @issue} ) %>
77 <%= submit_tag l(:button_delete) %>
78 <%= submit_tag l(:button_delete) %>
78 <%= end_form_tag %>
79 <%= end_form_tag %>
79 &nbsp;&nbsp;
80 &nbsp;&nbsp;
80 <% end %>
81 <% end %>
81 </p>
82 </div>
83 <div class="clear"></div>
82 </div>
84 </div>
83
85
84 <div class="box">
86 <div id="history" class="box">
85 <h3><%=l(:label_history)%></h3>
87 <h3><%=l(:label_history)%>
86 <table width="100%">
88 <% if @journals_count > @journals.length %>(<%= l(:label_last_changes, @journals.length) %>)<% end %></h3>
87 <% for history in @issue.histories.find(:all, :include => [:author, :status]) %>
89 <%= render :partial => 'history', :locals => { :journals => @journals } %>
88 <tr>
90 <% if @journals_count > @journals.length %>
89 <td><%= format_date(history.created_on) %></td>
91 <p><center><small>[ <%= link_to l(:label_change_view_all), :action => 'history', :id => @issue %> ]</small></center></p>
90 <td><%= history.author.display_name %></td>
91 <td><b><%= history.status.name %></b></td>
92 </tr>
93 <% if history.notes? %>
94 <tr><td colspan=3><%= simple_format auto_link history.notes %></td></tr>
95 <% end %>
96 <% end %>
92 <% end %>
97 </table>
98 </div>
93 </div>
99
94
100 <div class="box">
95 <div class="box">
@@ -130,9 +125,9 end %>
130 <div class="box">
125 <div class="box">
131 <h3><%= l(:label_add_note) %></h3>
126 <h3><%= l(:label_add_note) %></h3>
132 <%= start_form_tag ({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular" ) %>
127 <%= start_form_tag ({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular" ) %>
133 <p><label for="history_notes"><%=l(:field_notes)%></label>
128 <p><label for="notes"><%=l(:field_notes)%></label>
134 <%= text_area 'history', 'notes', :cols => 60, :rows => 10 %></p>
129 <%= text_area_tag 'notes', '', :cols => 60, :rows => 10 %></p>
135 <%= submit_tag l(:button_add) %>
130 <%= submit_tag l(:button_add) %>
136 <%= end_form_tag %>
131 <%= end_form_tag %>
137 </div>
132 </div>
138 <% end %>
133 <% end %>
@@ -133,7 +133,7 var menu_contenu=' \
133 <div id="footer">
133 <div id="footer">
134 <p>
134 <p>
135 <%= auto_link $RDM_FOOTER_SIG %> |
135 <%= auto_link $RDM_FOOTER_SIG %> |
136 <a href="http://redmine.org/" target="_new"><%= RDM_APP_NAME %></a> <%= RDM_APP_VERSION %>
136 <a href="http://redmine.rubyforge.org/" target="_new"><%= RDM_APP_NAME %></a> <%= RDM_APP_VERSION %>
137 </p>
137 </p>
138 </div>
138 </div>
139
139
@@ -7,6 +7,7 http://redmine.org/
7
7
8 == xx/xx/2006 v0.x.x
8 == xx/xx/2006 v0.x.x
9
9
10 * improved issues change history
10 * new functionality: move an issue to another project or tracker
11 * new functionality: move an issue to another project or tracker
11 * new functionality: add a note to an issue
12 * new functionality: add a note to an issue
12 * new report: project activity
13 * new report: project activity
@@ -256,6 +256,8 label_calendar: Kalender
256 label_months_from: Monate von
256 label_months_from: Monate von
257 label_gantt_chart: Gantt Diagramm
257 label_gantt_chart: Gantt Diagramm
258 label_internal: Intern
258 label_internal: Intern
259 label_last_changes: %d änderungen des Letzten
260 label_change_view_all: Alle änderungen ansehen
259
261
260 button_login: Einloggen
262 button_login: Einloggen
261 button_submit: Einreichen
263 button_submit: Einreichen
@@ -285,6 +287,9 text_possible_values_info: Werte trennten sich mit |
285 text_project_destroy_confirmation: Sind sie sicher, daß sie das Projekt löschen wollen ?
287 text_project_destroy_confirmation: Sind sie sicher, daß sie das Projekt löschen wollen ?
286 text_workflow_edit: Auswahl Workflow zum Bearbeiten
288 text_workflow_edit: Auswahl Workflow zum Bearbeiten
287 text_are_you_sure: Sind sie sicher ?
289 text_are_you_sure: Sind sie sicher ?
290 text_journal_changed: geändert von %s zu %s
291 text_journal_set_to: gestellt zu %s
292 text_journal_deleted: gelöscht
288
293
289 default_role_manager: Manager
294 default_role_manager: Manager
290 default_role_developper: Developer
295 default_role_developper: Developer
@@ -256,6 +256,8 label_calendar: Calendar
256 label_months_from: months from
256 label_months_from: months from
257 label_gantt_chart: Gantt chart
257 label_gantt_chart: Gantt chart
258 label_internal: Internal
258 label_internal: Internal
259 label_last_changes: last %d changes
260 label_change_view_all: View all changes
259
261
260 button_login: Login
262 button_login: Login
261 button_submit: Submit
263 button_submit: Submit
@@ -285,6 +287,9 text_possible_values_info: values separated with |
285 text_project_destroy_confirmation: Are you sure you want to delete this project and all related data ?
287 text_project_destroy_confirmation: Are you sure you want to delete this project and all related data ?
286 text_workflow_edit: Select a role and a tracker to edit the workflow
288 text_workflow_edit: Select a role and a tracker to edit the workflow
287 text_are_you_sure: Are you sure ?
289 text_are_you_sure: Are you sure ?
290 text_journal_changed: changed from %s to %s
291 text_journal_set_to: set to %s
292 text_journal_deleted: deleted
288
293
289 default_role_manager: Manager
294 default_role_manager: Manager
290 default_role_developper: Developer
295 default_role_developper: Developer
@@ -256,6 +256,8 label_calendar: Calendario
256 label_months_from: meses de
256 label_months_from: meses de
257 label_gantt_chart: Diagrama de Gantt
257 label_gantt_chart: Diagrama de Gantt
258 label_internal: Interno
258 label_internal: Interno
259 label_last_changes: %d cambios del último
260 label_change_view_all: Ver todos los cambios
259
261
260 button_login: Conexión
262 button_login: Conexión
261 button_submit: Someter
263 button_submit: Someter
@@ -285,6 +287,9 text_possible_values_info: Los valores se separaron con |
285 text_project_destroy_confirmation: ¿ Estás seguro de querer eliminar el proyecto ?
287 text_project_destroy_confirmation: ¿ Estás seguro de querer eliminar el proyecto ?
286 text_workflow_edit: Seleccionar un workflow para actualizar
288 text_workflow_edit: Seleccionar un workflow para actualizar
287 text_are_you_sure: ¿ Estás seguro ?
289 text_are_you_sure: ¿ Estás seguro ?
290 text_journal_changed: cambiado de %s a %s
291 text_journal_set_to: fijado a %s
292 text_journal_deleted: suprimido
288
293
289 default_role_manager: Manager
294 default_role_manager: Manager
290 default_role_developper: Desarrollador
295 default_role_developper: Desarrollador
@@ -257,6 +257,8 label_calendar: Calendrier
257 label_months_from: mois depuis
257 label_months_from: mois depuis
258 label_gantt_chart: Diagramme de Gantt
258 label_gantt_chart: Diagramme de Gantt
259 label_internal: Interne
259 label_internal: Interne
260 label_last_changes: %d derniers changements
261 label_change_view_all: Voir tous les changements
260
262
261 button_login: Connexion
263 button_login: Connexion
262 button_submit: Soumettre
264 button_submit: Soumettre
@@ -286,6 +288,9 text_possible_values_info: valeurs séparées par |
286 text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et tout ce qui lui est rattaché ?
288 text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et tout ce qui lui est rattaché ?
287 text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow
289 text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow
288 text_are_you_sure: Etes-vous sûr ?
290 text_are_you_sure: Etes-vous sûr ?
291 text_journal_changed: changé de %s à %s
292 text_journal_set_to: mis à %s
293 text_journal_deleted: supprimé
289
294
290 default_role_manager: Manager
295 default_role_manager: Manager
291 default_role_developper: Développeur
296 default_role_developper: Développeur
@@ -186,10 +186,6 form {
186
186
187 .noborder {
187 .noborder {
188 border:0px;
188 border:0px;
189 Exception exceptions.AssertionError: <exceptions.AssertionError instance at 0xb7c0b20c> in <bound
190 method SubversionRepository.__del__ of <vclib.svn.SubversionRepository instance at 0xb7c1252c>>
191 ignored
192
193 background-color:#fff;
189 background-color:#fff;
194 width:100%;
190 width:100%;
195 }
191 }
@@ -292,7 +288,7 table.calenderTable td {
292 border:1px solid #578bb8;
288 border:1px solid #578bb8;
293 }
289 }
294
290
295 hr { border:none; border-bottom: dotted 2px #c0c0c0; }
291 hr { border:none; border-bottom: dotted 1px #c0c0c0; }
296
292
297
293
298 /**************** Sidebar styles ****************/
294 /**************** Sidebar styles ****************/
@@ -409,6 +405,17 img.calendar-trigger {
409 margin-left: 4px;
405 margin-left: 4px;
410 }
406 }
411
407
408 #history h4 {
409 font-size: 1em;
410 margin-bottom: 12px;
411 margin-top: 20px;
412 font-weight: normal;
413 border-bottom: dotted 1px #c0c0c0;
414 }
415
416 #history p {
417 margin-left: 34px;
418 }
412
419
413 /***** CSS FORM ******/
420 /***** CSS FORM ******/
414 .tabular p{
421 .tabular p{
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now