improved issues change history

git-svn-id: http://redmine.rubyforge.org/svn/trunk@54 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2006-11-27 22:31:14 +00:00
parent 67e81b0ae9
commit 42181112ff
38 changed files with 389 additions and 153 deletions

View File

@ -41,7 +41,7 @@ class ApplicationController < ActionController::Base
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
self.logged_in_user.language
elsif request.env['HTTP_ACCEPT_LANGUAGE']
accept_lang = HTTPUtils.parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
accept_lang
end
@ -104,4 +104,23 @@ class ApplicationController < ActionController::Base
session[:return_to] = nil
end
end
# qvalues http header parser
# code taken from webrick
def parse_qvalues(value)
tmp = []
if value
parts = value.split(/,\s*/)
parts.each {|part|
if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
val = m[1]
q = (m[2] or 1).to_f
tmp.push([val, q])
end
}
tmp = tmp.sort_by{|val, q| -q}
tmp.collect!{|val, q| val}
end
return tmp
end
end

View File

@ -27,6 +27,13 @@ class IssuesController < ApplicationController
def show
@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
@custom_values = @issue.custom_values.find(:all, :include => :custom_field)
@journals_count = @issue.journals.count
@journals = @issue.journals.find(:all, :include => [:user, :details], :limit => 15, :order => "journals.created_on desc")
end
def history
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "journals.created_on desc")
@journals_count = @journals.length
end
def export_pdf
@ -41,6 +48,7 @@ class IssuesController < ApplicationController
@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
else
begin
@issue.init_journal(self.logged_in_user)
# Retrieve custom fields and values
@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]) }
@issue.custom_values = @custom_values
@ -57,13 +65,14 @@ class IssuesController < ApplicationController
end
def add_note
unless params[:history][:notes].empty?
@history = @issue.histories.build(params[:history])
@history.author_id = self.logged_in_user.id if self.logged_in_user
@history.status = @issue.status
if @history.save
unless params[:notes].empty?
journal = @issue.init_journal(self.logged_in_user, params[:notes])
#@history = @issue.histories.build(params[:history])
#@history.author_id = self.logged_in_user.id if self.logged_in_user
#@history.status = @issue.status
if @issue.save
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_add_note(@history) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue
return
end
@ -73,17 +82,20 @@ class IssuesController < ApplicationController
end
def change_status
@history = @issue.histories.build(params[:history])
#@history = @issue.histories.build(params[:history])
@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
@new_status = IssueStatus.find(params[:new_status_id])
if params[:confirm]
begin
@history.author_id = self.logged_in_user.id if self.logged_in_user
@issue.status = @history.status
@issue.fixed_version_id = (params[:issue][:fixed_version_id])
@issue.assigned_to_id = (params[:issue][:assigned_to_id])
@issue.done_ratio = (params[:issue][:done_ratio])
@issue.lock_version = (params[:issue][:lock_version])
if @issue.save
#@history.author_id = self.logged_in_user.id if self.logged_in_user
#@issue.status = @history.status
#@issue.fixed_version_id = (params[:issue][:fixed_version_id])
#@issue.assigned_to_id = (params[:issue][:assigned_to_id])
#@issue.done_ratio = (params[:issue][:done_ratio])
#@issue.lock_version = (params[:issue][:lock_version])
@issue.init_journal(self.logged_in_user, params[:notes])
@issue.status = @new_status
if @issue.update_attributes(params[:issue])
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_change_status(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue

View File

@ -28,6 +28,7 @@ class ProjectsController < ApplicationController
include CustomFieldsHelper
helper :ifpdf
include IfpdfHelper
helper IssuesHelper
def index
list

View File

@ -81,7 +81,7 @@ module ApplicationHelper
end
def textilizable(text)
$RDM_TEXTILE_DISABLED ? text : textilize(text)
$RDM_TEXTILE_DISABLED ? text : RedCloth.new(text).to_html
end
def error_messages_for(object_name, options = {})

View File

@ -54,15 +54,20 @@ module CustomFieldsHelper
# Return a string used to display a custom value
def show_value(custom_value)
return "" unless custom_value
case custom_value.custom_field.field_format
format_value(custom_value.value, custom_value.custom_field.field_format)
end
# Return a string used to display a custom value
def format_value(value, field_format)
return "" unless value
case field_format
when "date"
custom_value.value.empty? ? "" : l_date(custom_value.value.to_date)
value.empty? ? "" : l_date(value.to_date)
when "bool"
l_YesNo(custom_value.value == "1")
l_YesNo(value == "1")
else
custom_value.value
end
value
end
end
# Return an array of custom field formats which can be used in select_tag

View File

@ -25,12 +25,12 @@ module IfpdfHelper
def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
@ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
super w,h,@ic.iconv(txt),border,ln,align,fill,link
end
def MultiCell(w,h,txt,border=0,align='J',fill=0)
@ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
super w,h,txt,border,align,fill
txt = begin
@ic.iconv(txt)
rescue
txt
end
super w,h,txt,border,ln,align,fill,link
end
def Footer

View File

@ -15,5 +15,60 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module IssuesHelper
module IssuesHelper
def show_detail(detail, no_html=false)
case detail.property
when 'attr'
label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
case detail.prop_key
when 'due_date', 'start_date'
value = format_date(detail.value.to_date) if detail.value
old_value = format_date(detail.old_value.to_date) if detail.old_value
when 'status_id'
s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
when 'assigned_to_id'
u = User.find_by_id(detail.value) and value = u.name if detail.value
u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
when 'priority_id'
e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
when 'category_id'
c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
when 'fixed_version_id'
v = Version.find_by_id(detail.value) and value = v.name if detail.value
v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
end
when 'cf'
custom_field = CustomField.find_by_id(detail.prop_key)
if custom_field
label = custom_field.name
value = format_value(detail.value, custom_field.field_format) if detail.value
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
end
end
label ||= detail.prop_key
value ||= detail.value
old_value ||= detail.old_value
unless no_html
label = content_tag('strong', label)
old_value = content_tag("i", old_value) if old_value
old_value = content_tag("strike", old_value) if old_value and !value
value = content_tag("i", value) if value
end
if value
if old_value
label + " " + l(:text_journal_changed, old_value, value)
else
label + " " + l(:text_journal_set_to, value)
end
else
label + " " + l(:text_journal_deleted) + " (#{old_value})"
end
end
end

View File

@ -26,7 +26,8 @@ class Issue < ActiveRecord::Base
belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
#has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
has_many :journals, :as => :journalized, :dependent => true
has_many :attachments, :as => :container, :dependent => true
has_many :custom_values, :dependent => true, :as => :customized
@ -51,8 +52,28 @@ class Issue < ActiveRecord::Base
end
end
def before_create
build_history
#def before_create
# build_history
#end
def before_save
if @current_journal
# attributes changes
(Issue.column_names - %w(id description)).each {|c|
@current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => c,
:old_value => @issue_before_change.send(c),
:value => send(c)) unless send(c)==@issue_before_change.send(c)
}
# custom fields changes
custom_values.each {|c|
@current_journal.details << JournalDetail.new(:property => 'cf',
:prop_key => c.custom_field_id,
:old_value => @custom_values_before_change[c.custom_field_id],
:value => c.value) unless @custom_values_before_change[c.custom_field_id]==c.value
}
@current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
end
end
def long_id
@ -63,12 +84,20 @@ class Issue < ActiveRecord::Base
self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
return nil
end
def init_journal(user, notes = "")
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
@issue_before_change = self.clone
@custom_values_before_change = {}
self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
@current_journal
end
private
# Creates an history for the issue
def build_history
@history = self.histories.build
@history.status = self.status
@history.author = self.author
end
#def build_history
# @history = self.histories.build
# @history.status = self.status
# @history.author = self.author
#end
end

View File

@ -0,0 +1,22 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Journal < ActiveRecord::Base
belongs_to :journalized, :polymorphic => true
belongs_to :user
has_many :details, :class_name => "JournalDetail", :dependent => true
end

View File

@ -0,0 +1,20 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class JournalDetail < ActiveRecord::Base
belongs_to :journal
end

View File

@ -17,14 +17,6 @@
class Mailer < ActionMailer::Base
def issue_change_status(issue)
# Sends to all project members
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
@from = $RDM_MAIL_FROM
@subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
@body['issue'] = issue
end
def issue_add(issue)
# Sends to all project members
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
@ -33,12 +25,14 @@ class Mailer < ActionMailer::Base
@body['issue'] = issue
end
def issue_add_note(history)
def issue_edit(journal)
# Sends to all project members
@recipients = history.issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
issue = journal.journalized
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
@from = $RDM_MAIL_FROM
@subject = "[#{history.issue.project.name} - #{history.issue.tracker.name} ##{history.issue.id}] #{history.issue.status.name} - #{history.issue.subject}"
@body['history'] = history
@subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
@body['issue'] = issue
@body['journal']= journal
end
def lost_password(token)

View File

@ -0,0 +1,11 @@
<% for journal in journals %>
<h4><%= format_time(journal.created_on) %> - <%= journal.user.name %></h4>
<ul>
<% for detail in journal.details %>
<li><%= show_detail(detail) %></li>
<% end %>
</ul>
<% if journal.notes? %>
<%= simple_format auto_link journal.notes %>
<% end %>
<% end %>

View File

@ -66,29 +66,34 @@
pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
pdf.Ln
pdf.SetFont('Arial','B',9)
pdf.Cell(190,5, l(:label_history),"B")
pdf.Ln
for history in issue.histories.find(:all, :include => [:author, :status])
pdf.SetFont('Arial','B',8)
pdf.Cell(100,5, history.status.name)
pdf.SetFont('Arial','',8)
pdf.Cell(20,5, format_date(history.created_on))
pdf.Cell(70,5, history.author.name,0,0,"R")
pdf.SetFont('Arial','',8)
pdf.Cell(190,5, l(:label_history), "B")
pdf.Ln
for journal in issue.journals.find(:all, :include => :user, :order => "journals.created_on desc")
pdf.SetFont('Arial','B',8)
pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
pdf.Ln
pdf.Cell(10,4, "") and pdf.MultiCell(180,4, history.notes) if history.notes?
pdf.SetFont('Arial','I',8)
for detail in journal.details
pdf.Cell(190,5, "- " + show_detail(detail, true))
pdf.Ln
end
if journal.notes?
pdf.SetFont('Arial','',8)
pdf.MultiCell(190,5, journal.notes)
end
pdf.Ln
end
pdf.Ln
pdf.SetFont('Arial','B',9)
pdf.Cell(190,5, l(:label_attachment_plural), "B")
pdf.Ln
pdf.Ln
for attachment in issue.attachments
pdf.SetFont('Arial','',8)
pdf.Cell(80,5, attachment.filename)
pdf.Cell(20,5, human_size(attachment.filesize))
pdf.Cell(20,5, format_date(attachment.created_on))
pdf.Cell(20,5, human_size(attachment.filesize),0,0,"R")
pdf.Cell(20,5, format_date(attachment.created_on),0,0,"R")
pdf.Cell(70,5, attachment.author.name,0,0,"R")
pdf.Ln
end

View File

@ -1,13 +1,13 @@
<h2><%=l(:label_issue)%> #<%= @issue.id %>: <%= @issue.subject %></h2>
<%= error_messages_for 'history' %>
<%= error_messages_for 'issue' %>
<%= start_form_tag({:action => 'change_status', :id => @issue}, :class => "tabular") %>
<%= hidden_field_tag 'confirm', 1 %>
<%= hidden_field 'history', 'status_id' %>
<%= hidden_field_tag 'new_status_id', @new_status.id %>
<div class="box">
<p><label><%=l(:label_issue_status_new)%></label> <%= @history.status.name %></p>
<p><label><%=l(:label_issue_status_new)%></label> <%= @new_status.name %></p>
<p><label for="issue_assigned_to_id"><%=l(:field_assigned_to)%></label>
<select name="issue[assigned_to_id]">
@ -25,12 +25,13 @@
<select name="issue[fixed_version_id]">
<option value="">--none--</option>
<%= options_from_collection_for_select @issue.project.versions, "id", "name", @issue.fixed_version_id %>
</select></p>
<p><label for="history_notes"><%=l(:field_notes)%></label>
<%= text_area 'history', 'notes', :cols => 60, :rows => 10 %></p>
</div>
</select></p>
<p><label for="notes"><%= l(:field_notes) %></label>
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10 %></p>
</div>
<%= hidden_field 'issue', 'lock_version' %>
<%= submit_tag l(:button_save) %>
<%= end_form_tag %>

View File

@ -19,7 +19,7 @@
<div class="clear">
<p><%= f.text_field :subject, :size => 80, :required => true %></p>
<p><%= f.text_area :description, :cols => 60, :rows => 10, :required => true %></p>
<p><%= f.text_area :description, :cols => 60, :rows => [[10, @issue.description.length / 50].max, 100].min, :required => true %></p>
<% for @custom_value in @custom_values %>
<p><%= custom_field_tag_with_label @custom_value %></p>

View File

@ -0,0 +1,6 @@
<h3><%=l(:label_history)%></h3>
<div id="history">
<%= render :partial => 'history', :locals => { :journals => @journals } %>
</div>
<br />
<p><%= link_to l(:button_back), :action => 'show', :id => @issue %></p>

View File

@ -44,8 +44,8 @@ end %>
<b><%=l(:field_description)%> :</b><br /><br />
<%= textilizable @issue.description %>
<p>
<br />
<div style="float:left;">
<% if authorize_for('issues', 'edit') %>
<%= start_form_tag ({:controller => 'issues', :action => 'edit', :id => @issue}, :method => "get" ) %>
<%= submit_tag l(:button_edit) %>
@ -56,7 +56,7 @@ end %>
<% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
<%= start_form_tag ({:controller => 'issues', :action => 'change_status', :id => @issue}) %>
<%=l(:label_change_status)%> :
<select name="history[status_id]">
<select name="new_status_id">
<%= options_from_collection_for_select @status_options, "id", "name" %>
</select>
<%= submit_tag l(:button_change) %>
@ -71,30 +71,25 @@ end %>
<%= end_form_tag %>
&nbsp;&nbsp;
<% end %>
</div>
<div style="float:right;">
<% if authorize_for('issues', 'destroy') %>
<%= start_form_tag ({:controller => 'issues', :action => 'destroy', :id => @issue} ) %>
<%= submit_tag l(:button_delete) %>
<%= end_form_tag %>
&nbsp;&nbsp;
<% end %>
</p>
</div>
<div class="clear"></div>
</div>
<div class="box">
<h3><%=l(:label_history)%></h3>
<table width="100%">
<% for history in @issue.histories.find(:all, :include => [:author, :status]) %>
<tr>
<td><%= format_date(history.created_on) %></td>
<td><%= history.author.display_name %></td>
<td><b><%= history.status.name %></b></td>
</tr>
<% if history.notes? %>
<tr><td colspan=3><%= simple_format auto_link history.notes %></td></tr>
<% end %>
<div id="history" class="box">
<h3><%=l(:label_history)%>
<% if @journals_count > @journals.length %>(<%= l(:label_last_changes, @journals.length) %>)<% end %></h3>
<%= render :partial => 'history', :locals => { :journals => @journals } %>
<% if @journals_count > @journals.length %>
<p><center><small>[ <%= link_to l(:label_change_view_all), :action => 'history', :id => @issue %> ]</small></center></p>
<% end %>
</table>
</div>
<div class="box">
@ -130,9 +125,9 @@ end %>
<div class="box">
<h3><%= l(:label_add_note) %></h3>
<%= start_form_tag ({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular" ) %>
<p><label for="history_notes"><%=l(:field_notes)%></label>
<%= text_area 'history', 'notes', :cols => 60, :rows => 10 %></p>
<p><label for="notes"><%=l(:field_notes)%></label>
<%= text_area_tag 'notes', '', :cols => 60, :rows => 10 %></p>
<%= submit_tag l(:button_add) %>
<%= end_form_tag %>
<%= end_form_tag %>
</div>
<% end %>

View File

@ -133,7 +133,7 @@ var menu_contenu=' \
<div id="footer">
<p>
<%= auto_link $RDM_FOOTER_SIG %> |
<a href="http://redmine.org/" target="_new"><%= RDM_APP_NAME %></a> <%= RDM_APP_VERSION %>
<a href="http://redmine.rubyforge.org/" target="_new"><%= RDM_APP_NAME %></a> <%= RDM_APP_VERSION %>
</p>
</div>

View File

@ -1,3 +0,0 @@
Note added to issue #<%= @history.issue_id %> by <%= @history.author.name %>
----------------------------------------
<%= @history.notes %>

View File

@ -1,3 +0,0 @@
Note added to issue #<%= @history.issue_id %> by <%= @history.author.name %>
----------------------------------------
<%= @history.notes %>

View File

@ -1,3 +0,0 @@
Note added to issue #<%= @history.issue_id %> by <%= @history.author.name %>
----------------------------------------
<%= @history.notes %>

View File

@ -1,3 +0,0 @@
Note ajoutée à la demande #<%= @history.issue_id %> par <%= @history.author.name %>
----------------------------------------
<%= @history.notes %>

View File

@ -1,3 +0,0 @@
Issue #<%= @issue.id %> has been updated to "<%= @issue.status.name %>" status.
----------------------------------------
<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>

View File

@ -1,3 +0,0 @@
Issue #<%= @issue.id %> has been updated to "<%= @issue.status.name %>" status.
----------------------------------------
<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>

View File

@ -1,3 +0,0 @@
Issue #<%= @issue.id %> has been updated to "<%= @issue.status.name %>" status.
----------------------------------------
<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>

View File

@ -1,3 +0,0 @@
La demande #<%= @issue.id %> a été mise à jour au statut "<%= @issue.status.name %>".
----------------------------------------
<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>

View File

@ -0,0 +1,8 @@
Issue #<%= @issue.id %> has been updated.
<%= @journal.user.name %>
<% for detail in @journal.details %>
<%= show_detail(detail) %>
<% end %>
<%= @journal.notes if @journal.notes? %>
----------------------------------------
<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>

View File

@ -0,0 +1,8 @@
Issue #<%= @issue.id %> has been updated.
<%= @journal.user.name %>
<% for detail in @journal.details %>
<%= show_detail(detail) %>
<% end %>
<%= @journal.notes if @journal.notes? %>
----------------------------------------
<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>

View File

@ -0,0 +1,8 @@
Issue #<%= @issue.id %> has been updated.
<%= @journal.user.name %>
<% for detail in @journal.details %>
<%= show_detail(detail) %>
<% end %>
<%= @journal.notes if @journal.notes? %>
----------------------------------------
<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>

View File

@ -0,0 +1,8 @@
La demande #<%= @issue.id %> a été mise à jour.
<%= @journal.user.name %> - <%= format_date(@journal.created_on) %>
<% for detail in @journal.details %>
<%= show_detail(detail) %>
<% end %>
<%= journal.notes if journal.notes? %>
----------------------------------------
<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>

View File

@ -0,0 +1,54 @@
class CreateJournals < ActiveRecord::Migration
# model removed, but needed for data migration
class IssueHistory < ActiveRecord::Base; belongs_to :issue; end
def self.up
create_table :journals, :force => true do |t|
t.column "journalized_id", :integer, :default => 0, :null => false
t.column "journalized_type", :string, :limit => 30, :default => "", :null => false
t.column "user_id", :integer, :default => 0, :null => false
t.column "notes", :text
t.column "created_on", :datetime, :null => false
end
create_table :journal_details, :force => true do |t|
t.column "journal_id", :integer, :default => 0, :null => false
t.column "property", :string, :limit => 30, :default => "", :null => false
t.column "prop_key", :string, :limit => 30, :default => "", :null => false
t.column "old_value", :string
t.column "value", :string
end
# indexes
add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id"
add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id"
Permission.create :controller => "issues", :action => "history", :description => "label_history", :sort => 1006, :is_public => true, :mail_option => 0, :mail_enabled => 0
# data migration
IssueHistory.find(:all, :include => :issue).each {|h|
j = Journal.new(:journalized => h.issue, :user_id => h.author_id, :notes => h.notes, :created_on => h.created_on)
j.details << JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :value => h.status_id)
j.save
}
drop_table :issue_histories
end
def self.down
drop_table :journal_details
drop_table :journals
create_table "issue_histories", :force => true do |t|
t.column "issue_id", :integer, :default => 0, :null => false
t.column "status_id", :integer, :default => 0, :null => false
t.column "author_id", :integer, :default => 0, :null => false
t.column "notes", :text, :default => ""
t.column "created_on", :timestamp
end
add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id"
Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'history']).destroy
end
end

View File

@ -7,6 +7,7 @@ http://redmine.org/
== xx/xx/2006 v0.x.x
* improved issues change history
* new functionality: move an issue to another project or tracker
* new functionality: add a note to an issue
* new report: project activity

View File

@ -256,6 +256,8 @@ label_calendar: Kalender
label_months_from: Monate von
label_gantt_chart: Gantt Diagramm
label_internal: Intern
label_last_changes: %d änderungen des Letzten
label_change_view_all: Alle änderungen ansehen
button_login: Einloggen
button_submit: Einreichen
@ -285,6 +287,9 @@ text_possible_values_info: Werte trennten sich mit |
text_project_destroy_confirmation: Sind sie sicher, daß sie das Projekt löschen wollen ?
text_workflow_edit: Auswahl Workflow zum Bearbeiten
text_are_you_sure: Sind sie sicher ?
text_journal_changed: geändert von %s zu %s
text_journal_set_to: gestellt zu %s
text_journal_deleted: gelöscht
default_role_manager: Manager
default_role_developper: Developer

View File

@ -256,6 +256,8 @@ label_calendar: Calendar
label_months_from: months from
label_gantt_chart: Gantt chart
label_internal: Internal
label_last_changes: last %d changes
label_change_view_all: View all changes
button_login: Login
button_submit: Submit
@ -285,6 +287,9 @@ text_possible_values_info: values separated with |
text_project_destroy_confirmation: Are you sure you want to delete this project and all related data ?
text_workflow_edit: Select a role and a tracker to edit the workflow
text_are_you_sure: Are you sure ?
text_journal_changed: changed from %s to %s
text_journal_set_to: set to %s
text_journal_deleted: deleted
default_role_manager: Manager
default_role_developper: Developer

View File

@ -256,6 +256,8 @@ label_calendar: Calendario
label_months_from: meses de
label_gantt_chart: Diagrama de Gantt
label_internal: Interno
label_last_changes: %d cambios del último
label_change_view_all: Ver todos los cambios
button_login: Conexión
button_submit: Someter
@ -285,6 +287,9 @@ text_possible_values_info: Los valores se separaron con |
text_project_destroy_confirmation: ¿ Estás seguro de querer eliminar el proyecto ?
text_workflow_edit: Seleccionar un workflow para actualizar
text_are_you_sure: ¿ Estás seguro ?
text_journal_changed: cambiado de %s a %s
text_journal_set_to: fijado a %s
text_journal_deleted: suprimido
default_role_manager: Manager
default_role_developper: Desarrollador

View File

@ -257,6 +257,8 @@ label_calendar: Calendrier
label_months_from: mois depuis
label_gantt_chart: Diagramme de Gantt
label_internal: Interne
label_last_changes: %d derniers changements
label_change_view_all: Voir tous les changements
button_login: Connexion
button_submit: Soumettre
@ -286,6 +288,9 @@ text_possible_values_info: valeurs séparées par |
text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et tout ce qui lui est rattaché ?
text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow
text_are_you_sure: Etes-vous sûr ?
text_journal_changed: changé de %s à %s
text_journal_set_to: mis à %s
text_journal_deleted: supprimé
default_role_manager: Manager
default_role_developper: Développeur

View File

@ -186,10 +186,6 @@ form {
.noborder {
border:0px;
Exception exceptions.AssertionError: <exceptions.AssertionError instance at 0xb7c0b20c> in <bound
method SubversionRepository.__del__ of <vclib.svn.SubversionRepository instance at 0xb7c1252c>>
ignored
background-color:#fff;
width:100%;
}
@ -292,7 +288,7 @@ table.calenderTable td {
border:1px solid #578bb8;
}
hr { border:none; border-bottom: dotted 2px #c0c0c0; }
hr { border:none; border-bottom: dotted 1px #c0c0c0; }
/**************** Sidebar styles ****************/
@ -409,6 +405,17 @@ img.calendar-trigger {
margin-left: 4px;
}
#history h4 {
font-size: 1em;
margin-bottom: 12px;
margin-top: 20px;
font-weight: normal;
border-bottom: dotted 1px #c0c0c0;
}
#history p {
margin-left: 34px;
}
/***** CSS FORM ******/
.tabular p{

View File

@ -1,29 +0,0 @@
---
issue_histories_003:
created_on: 2006-07-19 21:07:27 +02:00
notes:
issue_id: 3
id: 3
author_id: 2
status_id: 1
issue_histories_004:
created_on: 2006-07-19 21:09:50 +02:00
notes: Should be bone quickly
issue_id: 2
id: 4
author_id: 2
status_id: 2
issue_histories_001:
created_on: 2006-07-19 21:02:17 +02:00
notes:
issue_id: 1
id: 1
author_id: 2
status_id: 1
issue_histories_002:
created_on: 2006-07-19 21:04:21 +02:00
notes:
issue_id: 2
id: 2
author_id: 2
status_id: 1