improved issues change history
git-svn-id: http://redmine.rubyforge.org/svn/trunk@54 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
67e81b0ae9
commit
42181112ff
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -28,6 +28,7 @@ class ProjectsController < ApplicationController
|
|||
include CustomFieldsHelper
|
||||
helper :ifpdf
|
||||
include IfpdfHelper
|
||||
helper IssuesHelper
|
||||
|
||||
def index
|
||||
list
|
||||
|
|
|
@ -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 = {})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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 %>
|
|
@ -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
|
||||
|
|
|
@ -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 %>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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 %>
|
||||
|
||||
<% 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 %>
|
||||
|
||||
<% 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 %>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
Note added to issue #<%= @history.issue_id %> by <%= @history.author.name %>
|
||||
----------------------------------------
|
||||
<%= @history.notes %>
|
|
@ -1,3 +0,0 @@
|
|||
Note added to issue #<%= @history.issue_id %> by <%= @history.author.name %>
|
||||
----------------------------------------
|
||||
<%= @history.notes %>
|
|
@ -1,3 +0,0 @@
|
|||
Note added to issue #<%= @history.issue_id %> by <%= @history.author.name %>
|
||||
----------------------------------------
|
||||
<%= @history.notes %>
|
|
@ -1,3 +0,0 @@
|
|||
Note ajoutée à la demande #<%= @history.issue_id %> par <%= @history.author.name %>
|
||||
----------------------------------------
|
||||
<%= @history.notes %>
|
|
@ -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 } %>
|
|
@ -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 } %>
|
|
@ -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 } %>
|
|
@ -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 } %>
|
|
@ -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 } %>
|
|
@ -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 } %>
|
|
@ -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 } %>
|
|
@ -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 } %>
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue